Compare commits

...

68 Commits
1663 ... 1703

Author SHA1 Message Date
George Politis
2b3aea76a9 doc: Nukes influxdb.md because influx support has been nuked. 2017-02-23 17:22:06 -06:00
Lyubo Marinov
f50a31b4e8 [RN] Simplify the source code 2017-02-23 17:14:04 -06:00
Saúl Ibarra Corretgé
b226c3aca3 [RN] Fix loading config.js from URLs with a non-standard port
`host` contains the hostname:port portion, whereas `hostname` is just the
hostname, not including the port.
2017-02-23 17:14:04 -06:00
yanas
4979666a89 Merge pull request #1342 from jitsi/filmstriponly_transparent
fix(filmstriponly): Set the background to transparent
2017-02-23 16:11:35 -06:00
yanas
c9636f85b9 Merge pull request #1314 from virtuacoplenny/lenny/audio-slider
Volume slider for remote participant audio elements
2017-02-23 15:48:52 -06:00
hristoterezov
436bc87a86 fix(overlay): comments after review 2017-02-23 13:50:09 -06:00
Lyubo Marinov
e89c2b242d Android plugin for Gradle 2.2.3
Recent versions of the Android plugin for Gradle started to automatically
download the SDK build tools dependency if it is not installed already.
So it is no longer necessary to have the developer of the Android app
install the SDK build tools dependency in advance.
2017-02-23 13:14:19 -06:00
Leonard Kim
02b26a65bb Volume slider for remote participant audio elements 2017-02-23 09:01:40 -08:00
Дамян Минков
6a3e4bb59f Merge pull request #1311 from saghul/resources
Move miscellaneous files to resources
2017-02-23 06:42:15 -08:00
Saúl Ibarra Corretgé
b01ad360da Move miscellaneous files to resources 2017-02-23 10:01:19 +01:00
Ilya Daynatovich
c7f3740099 Fix IE redirect problem 2017-02-22 23:36:06 -06:00
Дамян Минков
554595acd7 Merge pull request #1348 from jitsi/restyle-range-inputs
Re-styles range inputs for Chrome and FF.
2017-02-22 21:44:46 -06:00
yanas
ee4ddd5446 Fixes indentation 2017-02-22 17:14:09 -06:00
yanas
ebab617a12 Re-styles range inputs for Chrome and FF. 2017-02-22 16:49:56 -06:00
Lyubo Marinov
bc5d92a452 [RN] Prefer H.264 2017-02-22 08:28:19 -06:00
Saúl Ibarra Corretgé
2f388dfb6a Fix warning about missing key prop
When rendering using a for loop each child whould have a key prop.
2017-02-22 12:08:12 +01:00
Boris Grozev
73a0197eb2 doc: Adds a note on installing ios-deploy on MacOS 10.11. 2017-02-21 14:43:59 -06:00
hristoterezov
b6990e9e5d fix(filmstriponly): Set the background to transparent 2017-02-21 13:45:46 -06:00
Lyubo Marinov
26e119bfc2 Comply w/ coding style 2017-02-21 13:33:25 -06:00
Saúl Ibarra Corretgé
023359b9d2 [RN] Avoid rendering Container if not visible
This solves the issue of view clipping on Android, plus it seems to be the RN
convention unless the views are very large and memory hungry.
2017-02-21 11:13:36 -06:00
bgrozev
2128d047e1 Merge pull request #1339 from saghul/fix-doc-links
doc: fix markdown link syntax in mobile docs
2017-02-21 10:49:27 -06:00
Saúl Ibarra Corretgé
a89349c5b9 doc: fix markdown link syntax in mobile docs 2017-02-21 17:37:56 +01:00
Lyubo Marinov
d109b8beb6 Comply w/ coding style 2017-02-21 09:39:59 -06:00
Saúl Ibarra Corretgé
9b40572921 [RN] Fix Android immersive mode when coming from the background
Fixes an issue where immersive mode would be enabled when coming back from the
background on the welcome screen.

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

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

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

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

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

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

View File

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

View File

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

View File

@@ -20,6 +20,15 @@ import analytics from './modules/analytics/analytics';
import EventEmitter from "events";
import { conferenceFailed } from './react/features/base/conference';
import {
isFatalJitsiConnectionError
} from './react/features/base/lib-jitsi-meet';
import {
mediaPermissionPromptVisibilityChanged,
suspendDetected
} from './react/features/overlay';
const ConnectionEvents = JitsiMeetJS.events.connection;
const ConnectionErrors = JitsiMeetJS.errors.connection;
@@ -91,7 +100,10 @@ function createInitialLocalTracksAndConnect(roomName) {
JitsiMeetJS.mediaDevices.addEventListener(
JitsiMeetJS.events.mediaDevices.PERMISSION_PROMPT_IS_SHOWN,
browser => APP.UI.showUserMediaPermissionsGuidanceOverlay(browser));
browser =>
APP.store.dispatch(
mediaPermissionPromptVisibilityChanged(true, browser))
);
// First try to retrieve both audio and video.
let tryCreateLocalTracks = createLocalTracks(
@@ -109,8 +121,7 @@ function createInitialLocalTracksAndConnect(roomName) {
return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
.then(([tracks, con]) => {
APP.UI.hideUserMediaPermissionsGuidanceOverlay();
APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
if (audioAndVideoError) {
if (audioOnlyError) {
// If both requests for 'audio' + 'video' and 'audio' only
@@ -334,6 +345,7 @@ class ConferenceConnector {
this._reject(err);
}
_onConferenceFailed(err, ...params) {
APP.store.dispatch(conferenceFailed(room, err, ...params));
logger.error('CONFERENCE FAILED:', err, ...params);
APP.UI.hideRingOverLay();
switch (err) {
@@ -408,8 +420,6 @@ class ConferenceConnector {
// the app. Both the errors above are unrecoverable from the library
// perspective.
room.leave().then(() => connection.disconnect());
APP.UI.showPageReloadOverlay(
false /* not a network type of failure */, err);
break;
case ConferenceErrors.CONFERENCE_MAX_USERS:
@@ -466,6 +476,23 @@ function disconnect() {
return Promise.resolve();
}
/**
* Handles CONNECTION_FAILED events from lib-jitsi-meet.
*
* @param {JitsiMeetJS.connection.error} error - The reported error.
* @returns {void}
* @private
*/
function _connectionFailedHandler(error) {
if (isFatalJitsiConnectionError(error)) {
APP.connection.removeEventListener(
ConnectionEvents.CONNECTION_FAILED,
_connectionFailedHandler);
if (room)
room.leave();
}
}
export default {
isModerator: false,
audioMuted: false,
@@ -518,11 +545,13 @@ export default {
return createInitialLocalTracksAndConnect(options.roomName);
}).then(([tracks, con]) => {
logger.log('initialized with %s local tracks', tracks.length);
con.addEventListener(
ConnectionEvents.CONNECTION_FAILED,
_connectionFailedHandler);
APP.connection = connection = con;
this.isDesktopSharingEnabled =
JitsiMeetJS.isDesktopSharingEnabled();
APP.remoteControl.init();
this._bindConnectionFailedHandler(con);
this._createRoom(tracks);
if (UIUtil.isButtonEnabled('contacts')
@@ -561,47 +590,6 @@ export default {
isLocalId (id) {
return this.getMyUserId() === id;
},
/**
* Binds a handler that will handle the case when the connection is dropped
* in the middle of the conference.
* @param {JitsiConnection} connection the connection to which the handler
* will be bound to.
* @private
*/
_bindConnectionFailedHandler (connection) {
const handler = function (error, errMsg) {
/* eslint-disable no-case-declarations */
switch (error) {
case ConnectionErrors.CONNECTION_DROPPED_ERROR:
case ConnectionErrors.OTHER_ERROR:
case ConnectionErrors.SERVER_ERROR:
logger.error("XMPP connection error: " + errMsg);
// From all of the cases above only CONNECTION_DROPPED_ERROR
// is considered a network type of failure
const isNetworkFailure
= error === ConnectionErrors.CONNECTION_DROPPED_ERROR;
APP.UI.showPageReloadOverlay(
isNetworkFailure,
"xmpp-conn-dropped:" + errMsg);
connection.removeEventListener(
ConnectionEvents.CONNECTION_FAILED, handler);
// FIXME it feels like the conference should be stopped
// by lib-jitsi-meet
if (room)
room.leave();
break;
}
/* eslint-enable no-case-declarations */
};
connection.addEventListener(
ConnectionEvents.CONNECTION_FAILED, handler);
},
/**
* Simulates toolbar button click for audio mute. Used by shortcuts and API.
* @param mute true for mute and false for unmute.
@@ -676,7 +664,7 @@ export default {
* false.
*/
isCallstatsEnabled () {
return room.isCallstatsEnabled();
return room && room.isCallstatsEnabled();
},
/**
* Sends the given feedback through CallStats if enabled.
@@ -1229,7 +1217,8 @@ export default {
room.on(ConferenceEvents.TALK_WHILE_MUTED, () => {
APP.UI.showToolbar(6000);
UIUtil.animateShowElement($("#talkWhileMutedPopup"), true, 5000);
APP.UI.showCustomToolbarPopup('#talkWhileMutedPopup', true, 5000);
});
/*
@@ -1364,6 +1353,7 @@ export default {
});
room.on(ConferenceEvents.SUSPEND_DETECTED, () => {
APP.store.dispatch(suspendDetected());
// After wake up, we will be in a state where conference is left
// there will be dialog shown to user.
// We do not want video/audio as we show an overlay and after it
@@ -1384,9 +1374,6 @@ export default {
if (localAudio) {
localAudio.dispose();
}
// show overlay
APP.UI.showSuspendedOverlay();
});
room.on(ConferenceEvents.DTMF_SUPPORT_CHANGED, (isDTMFSupported) => {

View File

@@ -53,7 +53,6 @@ var config = { // eslint-disable-line no-unused-vars
disableStats: false,
disableAudioLevels: false,
channelLastN: -1, // The default value of the channel attribute last-n.
adaptiveLastN: false,
//disableAdaptiveSimulcast: false,
enableRecording: false,
enableWelcomePage: true,
@@ -80,5 +79,7 @@ var config = { // eslint-disable-line no-unused-vars
// Suspending video might cause problems with audio playback. Disabling until these are fixed.
disableSuspendVideo: true,
// disables or enables RTX (RFC 4588).
disableRtx: true
disableRtx: true,
// Sets the preferred resolution (height) for local video. Defaults to 360.
resolution: 720
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,7 @@ $defaultToolbarSize: 50px;
$thumbnailToolbarHeight: 22px;
$thumbnailIndicatorBorder: 2px;
$thumbnailIndicatorSize: $thumbnailToolbarHeight;
$thumbnailVideoMargin: 5px;
$thumbnailVideoMargin: 2px;
$thumbnailsBorder: 2px;
$thumbnailVideoBorder: 2px;
$hideFilmstripButtonWidth: 17px;

View File

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

View File

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

View File

@@ -60,7 +60,8 @@
@import 'components/link';
@import 'shortcuts/main';
@import 'components/button-control';
@import 'components/_input-control.scss';
@import 'components/input-control';
@import 'components/input-slider';
@import "modals/invite/invite";
@import "connection-info";
@import 'aui-components/dropdown';

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 611 B

View File

@@ -70,5 +70,7 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
AUDIO_LEVEL_SECONDARY_COLOR: "rgba(255,255,255,0.2)",
POLICY_LOGO: null,
LOCAL_THUMBNAIL_RATIO: 16/9, //16:9
REMOTE_THUMBNAIL_RATIO: 1 //1:1
REMOTE_THUMBNAIL_RATIO: 1, //1:1
// Documentation reference for the live streaming feature.
LIVE_STREAMING_HELP_LINK: "https://jitsi.org/live"
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

View File

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

View File

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

View File

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

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

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

View File

@@ -13,5 +13,6 @@
"sk": "Slovak",
"sl": "Slovenian",
"sv": "Swedish",
"tr": "Turkish"
"tr": "Turkish",
"zhCN": "Chinese (China)"
}

View File

@@ -154,7 +154,8 @@
"kick": "Hinauswerfen",
"muted": "Stummgeschaltet",
"domute": "Stummschalten",
"flip": "Spiegeln"
"flip": "Spiegeln",
"remoteControl": "Fernsteuerung"
},
"connectionindicator": {
"header": "Verbindungsdaten",
@@ -312,7 +313,12 @@
"externalInstallationMsg": "Die Bildschirmfreigabeerweiterung muss installiert werden.",
"muteParticipantTitle": "Teilnehmer stummschalten?",
"muteParticipantBody": "Sie können die Stummschaltung anderer Teilnehmer nicht aufheben, aber ein Teilnehmer kann seine eigene Stummschaltung jederzeit beenden.",
"muteParticipantButton": "Stummschalten"
"muteParticipantButton": "Stummschalten",
"remoteControlTitle": "Fernsteuerung",
"remoteControlDeniedMessage": "__user__ hat die Anfrage zur Fernsteuerung verweigert.",
"remoteControlAllowedMessage": "__user__ hat die Anfrage zur Fernsteuerung angenommen.",
"remoteControlErrorMessage": "Beim Anfordern der Fernsteuerungsberechtigung von __user__ ist ein Fehler aufgetreten.",
"remoteControlStopMessage": "Die Fernsteuerung wurde beendet."
},
"\u0005dialog": {},
"email": {
@@ -375,6 +381,7 @@
"failedToStart": "Live-Streaming konnte nicht gestartet werden",
"buttonTooltip": "Live-Stream starten / stoppen",
"streamIdRequired": "Bitte Stream-ID eingeben um das Live-Streaming zu starten.",
"streamIdHelp": "Wo ist die Stream-ID zu finden?",
"error": "Das Live-Streaming ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
"busy": "All Aufnahmegeräte sind momentan ausgelastet. Bitte versuchen Sie es später noch einmal."
}

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

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

View File

@@ -387,6 +387,7 @@
"failedToStart": "Live streaming failed to start",
"buttonTooltip": "Start / Stop live stream",
"streamIdRequired": "Please fill in the stream id in order to launch the live streaming.",
"streamIdHelp": "Where do I find this?",
"error": "Live streaming failed. Please try again.",
"busy": "All recorders are currently busy. Please try again later."
}

View File

@@ -15,18 +15,13 @@ import UIEvents from "../../service/UI/UIEvents";
import EtherpadManager from './etherpad/Etherpad';
import SharedVideoManager from './shared_video/SharedVideo';
import Recording from "./recording/Recording";
import GumPermissionsOverlay
from './gum_overlay/UserMediaPermissionsGuidanceOverlay';
import * as PageReloadOverlay from './reload_overlay/PageReloadOverlay';
import SuspendedOverlay from './suspended_overlay/SuspendedOverlay';
import VideoLayout from "./videolayout/VideoLayout";
import FilmStrip from "./videolayout/FilmStrip";
import SettingsMenu from "./side_pannels/settings/SettingsMenu";
import Profile from "./side_pannels/profile/Profile";
import Settings from "./../settings/Settings";
import RingOverlay from "./ring_overlay/RingOverlay";
import { randomInt } from "../../react/features/base/util/randomUtil";
import UIErrors from './UIErrors';
import { debounce } from "../util/helpers";
@@ -40,6 +35,17 @@ import FollowMe from "../FollowMe";
var eventEmitter = new EventEmitter();
UI.eventEmitter = eventEmitter;
/**
* Whether an overlay is visible or not.
*
* FIXME: This is temporary solution. Don't use this variable!
* Should be removed when all the code is move to react.
*
* @type {boolean}
* @public
*/
UI.overlayVisible = false;
let etherpadManager;
let sharedVideoManager;
@@ -435,6 +441,7 @@ UI.start = function () {
UIUtil.setVisible('notice', true);
}
} else {
$("body").addClass("filmstrip-only");
UIUtil.setVisible('mainToolbarContainer', false);
FilmStrip.setupFilmStripOnly();
messageHandler.enableNotifications(false);
@@ -712,6 +719,17 @@ UI.inputDisplayNameHandler = function (newDisplayName) {
eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newDisplayName);
};
/**
* Show custom popup/tooltip for a specified button.
* @param popupSelectorID the selector id of the popup to show
* @param show true or false/show or hide the popup
* @param timeout the time to show the popup
*/
UI.showCustomToolbarPopup = function (popupSelectorID, show, timeout) {
eventEmitter.emit(UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP,
popupSelectorID, show, timeout);
};
/**
* Return the type of the remote video.
* @param jid the jid for the remote video
@@ -1076,20 +1094,6 @@ UI.notifyFocusDisconnected = function (focus, retrySec) {
);
};
/**
* Notify the user that the video conferencing service is badly broken and
* the page should be reloaded.
*
* @param {boolean} isNetworkFailure <tt>true</tt> indicates that it's caused by
* network related failure or <tt>false</tt> when it's the infrastructure.
* @param {string} a label string identifying the reason for the page reload
* which will be included in details of the log event.
*/
UI.showPageReloadOverlay = function (isNetworkFailure, reason) {
// Reload the page after 10 - 30 seconds
PageReloadOverlay.show(10 + randomInt(0, 20), isNetworkFailure, reason);
};
/**
* Updates auth info on the UI.
* @param {boolean} isAuthEnabled if authentication is enabled
@@ -1403,10 +1407,7 @@ UI.hideRingOverLay = function () {
* @returns {*|boolean} {true} if the overlay is visible, {false} otherwise
*/
UI.isOverlayVisible = function () {
return RingOverlay.isVisible()
|| SuspendedOverlay.isVisible()
|| PageReloadOverlay.isVisible()
|| GumPermissionsOverlay.isVisible();
return RingOverlay.isVisible() || this.overlayVisible;
};
/**
@@ -1418,29 +1419,6 @@ UI.isRingOverlayVisible = function () {
return RingOverlay.isVisible();
};
/**
* Shows browser-specific overlay with guidance how to proceed with gUM prompt.
* @param {string} browser - name of browser for which to show the guidance
* overlay.
*/
UI.showUserMediaPermissionsGuidanceOverlay = function (browser) {
GumPermissionsOverlay.show(browser);
};
/**
* Shows suspended overlay with a button to rejoin conference.
*/
UI.showSuspendedOverlay = function () {
SuspendedOverlay.show();
};
/**
* Hides browser-specific overlay with guidance how to proceed with gUM prompt.
*/
UI.hideUserMediaPermissionsGuidanceOverlay = function () {
GumPermissionsOverlay.hide();
};
/**
* Handles user's features changes.
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
/* global APP, $, config, interfaceConfig, JitsiMeetJS */
/* global AJS, APP, $, config, interfaceConfig, JitsiMeetJS */
import UIUtil from '../util/UIUtil';
import UIEvents from '../../../service/UI/UIEvents';
import SideContainerToggler from "../side_pannels/SideContainerToggler";
@@ -26,8 +26,8 @@ const buttonHandlers = {
if (sharedVideoManager
&& sharedVideoManager.isSharedVideoVolumeOn()
&& !sharedVideoManager.isSharedVideoOwner()) {
UIUtil.animateShowElement(
$("#unableToUnmutePopup"), true, 5000);
APP.UI.showCustomToolbarPopup(
'#unableToUnmutePopup', true, 5000);
}
else {
JitsiMeetJS.analytics.sendEvent('toolbar.audio.unmuted');
@@ -120,19 +120,19 @@ const defaultToolbarButtons = {
shortcutDescription: "keyboardShortcuts.mute",
popups: [
{
id: "micMutedPopup",
className: "loginmenu",
dataAttr: "[html]toolbar.micMutedPopup"
id: 'micMutedPopup',
className: 'loginmenu',
dataAttr: '[title]toolbar.micMutedPopup'
},
{
id: "unableToUnmutePopup",
className: "loginmenu",
dataAttr: "[html]toolbar.unableToUnmutePopup"
id: 'unableToUnmutePopup',
className: 'loginmenu',
dataAttr: '[title]toolbar.unableToUnmutePopup'
},
{
id: "talkWhileMutedPopup",
className: "loginmenu",
dataAttr: "[html]toolbar.talkWhileMutedPopup"
id: 'talkWhileMutedPopup',
className: 'loginmenu',
dataAttr: '[title]toolbar.talkWhileMutedPopup'
}
],
content: "Mute / Unmute",
@@ -263,11 +263,14 @@ const defaultToolbarButtons = {
id: 'toolbar_button_sharedvideo',
tooltipKey: 'toolbar.sharedvideo',
className: 'button icon-shared-video',
html: `<ul id="sharedVideoMutedPopup"
class="loginmenu extendedToolbarPopup">
<li data-i18n="[html]toolbar.sharedVideoMutedPopup"></li>
</ul>
`
popups: [
{
id: 'sharedVideoMutedPopup',
className: 'loginmenu extendedToolbarPopup',
dataAttr: '[title]toolbar.sharedVideoMutedPopup',
dataAttrPosition: 'w'
}
]
},
'sip': {
id: 'toolbar_button_sip',
@@ -358,6 +361,11 @@ Toolbar = {
Toolbar._handleFullScreenToggled(isFullScreen);
});
APP.UI.addListener(UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP,
(popupID, show, timeout) => {
Toolbar._showCustomToolbarPopup(popupID, show, timeout);
});
if(!APP.tokenData.isGuest) {
$("#toolbar_button_profile").addClass("unclickable");
UIUtil.removeTooltip(
@@ -724,17 +732,52 @@ Toolbar = {
_addPopups(buttonElement, popups = []) {
popups.forEach((popup) => {
let popupElement = document.createElement("ul");
const popupElement = document.createElement('div');
popupElement.id = popup.id;
popupElement.className = popup.className;
let liElement = document.createElement("li");
liElement.setAttribute("data-i18n", popup.dataAttr);
popupElement.appendChild(liElement);
popupElement.setAttribute('data-i18n', popup.dataAttr);
let gravity = 'n';
if (popup.dataAttrPosition)
gravity = popup.dataAttrPosition;
// use custom attribute to save gravity option
// we use 'data-tooltip' in UIUtil to activate all tooltips
// but we want these to be manually triggered
popupElement.setAttribute('tooltip-gravity', gravity);
APP.translation.translateElement($(popupElement));
buttonElement.appendChild(popupElement);
});
},
/**
* Show custom popup/tooltip for a specified button.
* @param popupSelectorID the selector id of the popup to show
* @param show true or false/show or hide the popup
* @param timeout the time to show the popup
*/
_showCustomToolbarPopup(popupSelectorID, show, timeout) {
const gravity = $(popupSelectorID).attr('tooltip-gravity');
AJS.$(popupSelectorID)
.tooltip({
trigger: 'manual',
html: true,
gravity: gravity,
title: 'title'});
if (show) {
AJS.$(popupSelectorID).tooltip('show');
setTimeout(function () {
// hide the tooltip
AJS.$(popupSelectorID).tooltip('hide');
}, timeout);
} else {
AJS.$(popupSelectorID).tooltip('hide');
}
},
/**
* Sets the toggled state of the given element depending on the isToggled
* parameter.
*

View File

@@ -28,6 +28,7 @@ function RemoteVideo(user, VideoLayout, emitter) {
this.emitter = emitter;
this.videoSpanId = `participant_${this.id}`;
SmallVideo.call(this, VideoLayout);
this._audioStreamElement = null;
this.hasRemoteVideoMenu = false;
this._supportsRemoteControl = false;
this.addRemoteVideoContainer();
@@ -200,6 +201,18 @@ RemoteVideo.prototype._generatePopupContent = function () {
popupmenuElement.appendChild(menuItem);
});
// feature check for volume setting as temasys objects cannot adjust volume
if (this._canSetAudioVolume()) {
const volumeScale = 100;
const volumeSlider = this._generatePopupMenuSliderItem({
handler: this._setAudioVolume.bind(this, volumeScale),
icon: 'icon-volume',
initialValue: this._getAudioElement().volume * volumeScale,
maxValue: volumeScale
});
popupmenuElement.appendChild(volumeSlider);
}
APP.translation.translateElement($(popupmenuElement));
return popupmenuElement;
@@ -343,6 +356,74 @@ RemoteVideo.prototype._generatePopupMenuItem = function (opts = {}) {
return menuItem;
};
/**
* Create a div element with a slider.
*
* @param {object} options - Configuration for the div's display and slider.
* @param {string} options.icon - The classname for the icon to display.
* @param {int} options.maxValue - The maximum value on the slider. The default
* value is 100.
* @param {int} options.initialValue - The value the slider should start at.
* The default value is 0.
* @param {function} options.handler - The callback for slider value changes.
* @returns {Element} A div element with a slider.
*/
RemoteVideo.prototype._generatePopupMenuSliderItem = function (options) {
const template = `<div class='popupmenu__contents'>
<span class='popupmenu__icon'>
<i class=${options.icon}></i>
</span>
<input class='popupmenu__slider'
type='range'
min='0'
max=${options.maxValue || 100}
value=${options.initialValue || 0}>
</input>
</div>`;
const menuItem = document.createElement('li');
menuItem.className = 'popupmenu__item';
menuItem.innerHTML = template;
const slider = menuItem.getElementsByClassName('popupmenu__slider')[0];
slider.oninput = function () {
options.handler(Number(slider.value));
};
return menuItem;
};
/**
* Get the remote participant's audio element.
*
* @returns {Element} audio element
*/
RemoteVideo.prototype._getAudioElement = function () {
return this._audioStreamElement;
};
/**
* Check if the remote participant's audio can have its volume adjusted.
*
* @returns {boolean} true if the volume can be adjusted.
*/
RemoteVideo.prototype._canSetAudioVolume = function () {
const audioElement = this._getAudioElement();
return audioElement && audioElement.volume !== undefined;
};
/**
* Change the remote participant's volume level.
*
* @param {int} scale - The maximum value the slider can go to.
* @param {int} newVal - The value to set the slider to.
*/
RemoteVideo.prototype._setAudioVolume = function (scale, newVal) {
if (this._canSetAudioVolume()) {
this._getAudioElement().volume = newVal / scale;
}
};
/**
* Updates the remote video menu.
*
@@ -613,6 +694,10 @@ RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
}
$(streamElement).click(onClickHandler);
if (!isVideo) {
this._audioStreamElement = streamElement;
}
};
/**

View File

@@ -35,6 +35,7 @@
"react": "15.4.2",
"react-dom": "15.4.2",
"react-native": "0.41.2",
"react-native-background-timer": "1.0.0",
"react-native-immersive": "0.0.4",
"react-native-keep-awake": "^2.0.2",
"react-native-prompt": "^1.0.0",
@@ -66,7 +67,7 @@
"eslint-plugin-jsdoc": "*",
"eslint-plugin-react": "*",
"eslint-plugin-react-native": "^2.2.1",
"expose-loader": "*",
"expose-loader": "0.7.1",
"file-loader": "^0.10.0",
"flow-bin": "^0.37.0",
"haste-resolver-webpack-plugin": "^0.2.2",

View File

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

View File

@@ -1,3 +1,5 @@
/* global APP */
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { compose, createStore } from 'redux';
@@ -76,7 +78,9 @@ export class AbstractApp extends Component {
dispatch(localParticipantJoined());
this._openURL(this._getDefaultURL());
// If a URL was explicitly specified to this React Component, then open
// it; otherwise, use a default.
this._openURL(this.props.url || this._getDefaultURL());
}
/**
@@ -211,27 +215,20 @@ export class AbstractApp extends Component {
/**
* Gets the default URL to be opened when this App mounts.
*
* @private
* @protected
* @returns {string} The default URL to be opened when this App mounts.
*/
_getDefaultURL() {
// If the URL was explicitly specified to the React Component, then open
// it.
let url = this.props.url;
if (url) {
return url;
}
// If the execution environment provides a Location abstraction, then
// this App at already at that location but it must be made aware of the
// fact.
const windowLocation = this._getWindowLocation();
if (windowLocation) {
url = windowLocation.toString();
if (url) {
return url;
const href = windowLocation.toString();
if (href) {
return href;
}
}
@@ -305,6 +302,14 @@ export class AbstractApp extends Component {
if (typeof store === 'undefined') {
store = this._createStore();
// This is temporary workaround to be able to dispatch actions from
// non-reactified parts of the code (conference.js for example).
// Don't use in the react code!!!
// FIXME: remove when the reactification is finished!
if (typeof APP !== 'undefined') {
APP.store = store;
}
}
return store;

View File

@@ -2,8 +2,9 @@
import { Linking } from 'react-native';
import { Platform } from '../../base/react';
import '../../audio-mode';
import '../../background';
import { Platform } from '../../base/react';
import '../../full-screen';
import '../../wake-lock';

View File

@@ -126,7 +126,7 @@ function _getRoomAndDomainFromURLObject(url) {
let room;
if (url) {
domain = url.hostname;
domain = url.host;
// The room (name) is the last component of pathname.
room = url.pathname;

View File

@@ -1,6 +1,7 @@
/* global APP, JitsiMeetJS, loggingConfig */
/* global APP, loggingConfig */
import { isRoomValid } from '../base/conference';
import JitsiMeetJS from '../base/lib-jitsi-meet';
import { RouteRegistry } from '../base/react';
import { interceptComponent } from '../base/util';
import { Conference } from '../conference';

View File

@@ -2,3 +2,5 @@ export * from './actions';
export * from './actionTypes';
export * from './components';
export * from './functions';
import './reducer';

View File

@@ -0,0 +1,43 @@
import { Symbol } from '../base/react';
/**
* The type of redux action to set the AppState API change event listener.
*
* {
* type: _SET_APP_STATE_LISTENER,
* listener: Function
* }
*
* @protected
*/
export const _SET_APP_STATE_LISTENER
= Symbol('_SET_APP_STATE_LISTENER');
/**
* The type of redux action which signals that video will be muted because the
* app is going to the background.
*
* {
* type: _SET_BACKGROUND_VIDEO_MUTED,
* muted: boolean
* }
*
* @protected
*/
export const _SET_BACKGROUND_VIDEO_MUTED
= Symbol('_SET_BACKGROUND_VIDEO_MUTED');
/**
* The type of redux action which signals that the app state has changed (in
* terms of execution mode). The app state can be one of 'active', 'inactive',
* or 'background'.
*
* {
* type: APP_STATE_CHANGED,
* appState: string
* }
*
* @public
* @see {@link https://facebook.github.io/react-native/docs/appstate.html}
*/
export const APP_STATE_CHANGED = Symbol('APP_STATE_CHANGED');

View File

@@ -0,0 +1,81 @@
import { setVideoMuted } from '../base/media';
import {
_SET_APP_STATE_LISTENER,
_SET_BACKGROUND_VIDEO_MUTED,
APP_STATE_CHANGED
} from './actionTypes';
/**
* Signals that the App state has changed (in terms of execution state). The
* application can be in 3 states: 'active', 'inactive' and 'background'.
*
* @param {string} appState - The new App state.
* @public
* @returns {{
* type: APP_STATE_CHANGED,
* appState: string
* }}
* @see {@link https://facebook.github.io/react-native/docs/appstate.html}
*/
export function appStateChanged(appState: string) {
return {
type: APP_STATE_CHANGED,
appState
};
}
/**
* Sets the listener to be used with React Native's AppState API.
*
* @param {Function} listener - Function to be set as the change event listener.
* @protected
* @returns {{
* type: _SET_APP_STATE_LISTENER,
* listener: Function
* }}
*/
export function _setAppStateListener(listener: ?Function) {
return {
type: _SET_APP_STATE_LISTENER,
listener
};
}
/**
* Signals that the app should mute video because it's now running in the
* background, or unmute it because it came back from the background. If video
* was already muted nothing will happen; otherwise, it will be muted. When
* coming back from the background the previous state will be restored.
*
* @param {boolean} muted - True if video should be muted; false, otherwise.
* @protected
* @returns {Function}
*/
export function _setBackgroundVideoMuted(muted: boolean) {
return (dispatch, getState) => {
if (muted) {
const mediaState = getState()['features/base/media'];
if (mediaState.video.muted) {
// Video is already muted, do nothing.
return;
}
} else {
const bgState = getState()['features/background'];
if (!bgState.videoMuted) {
// We didn't mute video, do nothing.
return;
}
}
// Remember that video was muted due to the app going to the background
// vs user's choice.
dispatch({
type: _SET_BACKGROUND_VIDEO_MUTED,
muted
});
dispatch(setVideoMuted(muted));
};
}

View File

@@ -0,0 +1,5 @@
export * from './actions';
export * from './actionTypes';
import './middleware';
import './reducer';

View File

@@ -0,0 +1,109 @@
/* @flow */
import { AppState } from 'react-native';
import type { Dispatch } from 'redux';
import {
APP_WILL_MOUNT,
APP_WILL_UNMOUNT
} from '../app';
import { MiddlewareRegistry } from '../base/redux';
import {
_setAppStateListener,
_setBackgroundVideoMuted,
appStateChanged
} from './actions';
import {
_SET_APP_STATE_LISTENER,
APP_STATE_CHANGED
} from './actionTypes';
/**
* Middleware that captures App lifetime actions and subscribes to application
* state changes. When the application state changes it will fire the action
* required to mute or unmute the local video in case the application goes to
* the background or comes back from it.
*
* @param {Store} store - Redux store.
* @returns {Function}
* @see {@link https://facebook.github.io/react-native/docs/appstate.html}
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case _SET_APP_STATE_LISTENER: {
// Remove the current/old AppState listener.
const { appStateListener } = store.getState()['features/background'];
if (appStateListener) {
AppState.removeEventListener('change', appStateListener);
}
// Add the new AppState listener.
if (action.listener) {
AppState.addEventListener('change', action.listener);
}
break;
}
case APP_STATE_CHANGED:
_appStateChanged(store.dispatch, action.appState);
break;
case APP_WILL_MOUNT:
store.dispatch(
_setAppStateListener(
_onAppStateChange.bind(undefined, store.dispatch)));
break;
case APP_WILL_UNMOUNT:
store.dispatch(_setAppStateListener(null));
break;
}
return next(action);
});
/**
* Handles app state changes. Dispatches the necessary Redux actions for the
* local video to be muted when the app goes to the background, and to be
* unmuted when the app comes back.
*
* @param {Dispatch} dispatch - Redux dispatch function.
* @param {string} appState - The current app state.
* @private
* @returns {void}
*/
function _appStateChanged(dispatch: Dispatch<*>, appState: string) {
let muted;
switch (appState) {
case 'active':
muted = false;
break;
case 'background':
muted = true;
break;
case 'inactive':
default:
// XXX: We purposely don't handle the 'inactive' app state.
return;
}
dispatch(_setBackgroundVideoMuted(muted));
}
/**
* Called by React Native's AppState API to notify that the application state
* has changed. Dispatches the change within the (associated) Redux store.
*
* @param {Dispatch} dispatch - Redux dispatch function.
* @param {string} appState - The current application execution state.
* @private
* @returns {void}
*/
function _onAppStateChange(dispatch: Dispatch<*>, appState: string) {
dispatch(appStateChanged(appState));
}

View File

@@ -0,0 +1,31 @@
import { ReducerRegistry } from '../base/redux';
import {
_SET_APP_STATE_LISTENER,
_SET_BACKGROUND_VIDEO_MUTED,
APP_STATE_CHANGED
} from './actionTypes';
ReducerRegistry.register('features/background', (state = {}, action) => {
switch (action.type) {
case _SET_APP_STATE_LISTENER:
return {
...state,
appStateListener: action.listener
};
case _SET_BACKGROUND_VIDEO_MUTED:
return {
...state,
videoMuted: action.muted
};
case APP_STATE_CHANGED:
return {
...state,
appState: action.appState
};
}
return state;
});

View File

@@ -20,8 +20,6 @@ import {
} from './actionTypes';
import { EMAIL_COMMAND } from './constants';
import { _addLocalTracksToConference } from './functions';
import './middleware';
import './reducer';
/**
* Adds conference (event) listeners.
@@ -36,7 +34,7 @@ function _addConferenceListeners(conference, dispatch) {
conference.on(
JitsiConferenceEvents.CONFERENCE_FAILED,
(...args) => dispatch(_conferenceFailed(conference, ...args)));
(...args) => dispatch(conferenceFailed(conference, ...args)));
conference.on(
JitsiConferenceEvents.CONFERENCE_JOINED,
(...args) => dispatch(_conferenceJoined(conference, ...args)));
@@ -89,8 +87,9 @@ function _addConferenceListeners(conference, dispatch) {
* conference: JitsiConference,
* error: string
* }}
* @public
*/
function _conferenceFailed(conference, error) {
export function conferenceFailed(conference, error) {
return {
type: CONFERENCE_FAILED,
conference,

View File

@@ -19,8 +19,8 @@ export function _addLocalTracksToConference(conference, localTracks) {
// XXX The library lib-jitsi-meet may be draconian, for example, when
// adding one and the same video track multiple times.
if (conferenceLocalTracks.indexOf(track) === -1) {
promises.push(conference.addTrack(track)
.catch(err => {
promises.push(
conference.addTrack(track).catch(err => {
_reportError(
'Failed to add local track to conference',
err);

View File

@@ -1,3 +1,6 @@
export * from './actions';
export * from './actionTypes';
export * from './functions';
import './middleware';
import './reducer';

View File

@@ -1,3 +1,4 @@
/* global APP */
import { CONNECTION_ESTABLISHED } from '../connection';
import {
getLocalParticipant,
@@ -53,7 +54,11 @@ MiddlewareRegistry.register(store => next => action => {
function _connectionEstablished(store, next, action) {
const result = next(action);
store.dispatch(createConference());
// FIXME: workaround for the web version. Currently the creation of the
// conference is handled by /conference.js
if (typeof APP === 'undefined') {
store.dispatch(createConference());
}
return result;
}

View File

@@ -11,7 +11,6 @@ import {
CONNECTION_FAILED,
SET_DOMAIN
} from './actionTypes';
import './reducer';
const JitsiConnectionEvents = JitsiMeetJS.events.connection;
@@ -23,17 +22,16 @@ const JitsiConnectionEvents = JitsiMeetJS.events.connection;
export function connect() {
return (dispatch: Dispatch<*>, getState: Function) => {
const state = getState();
const connectionOptions
= state['features/base/connection'].connectionOptions;
const room = state['features/base/conference'].room;
const { options } = state['features/base/connection'];
const { room } = state['features/base/conference'];
const connection
= new JitsiMeetJS.JitsiConnection(
connectionOptions.appId,
connectionOptions.token,
options.appId,
options.token,
{
...connectionOptions,
...options,
bosh:
connectionOptions.bosh
options.bosh
// XXX The Jitsi Meet deployments require the room
// argument to be in lower case at the time of this
@@ -44,13 +42,13 @@ export function connect() {
connection.addEventListener(
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
connectionDisconnected);
_onConnectionDisconnected);
connection.addEventListener(
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
connectionEstablished);
_onConnectionEstablished);
connection.addEventListener(
JitsiConnectionEvents.CONNECTION_FAILED,
connectionFailed);
_onConnectionFailed);
connection.connect();
@@ -60,11 +58,12 @@ export function connect() {
*
* @param {string} message - Disconnect reason.
* @returns {void}
* @private
*/
function connectionDisconnected(message: string) {
function _onConnectionDisconnected(message: string) {
connection.removeEventListener(
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
connectionDisconnected);
_onConnectionDisconnected);
dispatch(_connectionDisconnected(connection, message));
}
@@ -73,10 +72,11 @@ export function connect() {
* Resolves external promise when connection is established.
*
* @returns {void}
* @private
*/
function connectionEstablished() {
function _onConnectionEstablished() {
unsubscribe();
dispatch(_connectionEstablished(connection));
dispatch(connectionEstablished(connection));
}
/**
@@ -84,11 +84,12 @@ export function connect() {
*
* @param {JitsiConnectionErrors} err - Connection error.
* @returns {void}
* @private
*/
function connectionFailed(err) {
function _onConnectionFailed(err) {
unsubscribe();
console.error('CONNECTION FAILED:', err);
dispatch(_connectionFailed(connection, err));
dispatch(connectionFailed(connection, err, ''));
}
/**
@@ -100,10 +101,10 @@ export function connect() {
function unsubscribe() {
connection.removeEventListener(
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
connectionEstablished);
_onConnectionEstablished);
connection.removeEventListener(
JitsiConnectionEvents.CONNECTION_FAILED,
connectionFailed);
_onConnectionFailed);
}
};
}
@@ -184,13 +185,13 @@ function _connectionDisconnected(connection, message: string) {
*
* @param {JitsiConnection} connection - The JitsiConnection which was
* established.
* @private
* @returns {{
* type: CONNECTION_ESTABLISHED,
* connection: JitsiConnection
* }}
* @public
*/
function _connectionEstablished(connection) {
export function connectionEstablished(connection: Object) {
return {
type: CONNECTION_ESTABLISHED,
connection
@@ -201,18 +202,22 @@ function _connectionEstablished(connection) {
* Create an action for when the signaling connection could not be created.
*
* @param {JitsiConnection} connection - The JitsiConnection which failed.
* @param {string} error - Error message.
* @private
* @param {string} error - Error.
* @param {string} errorMessage - Error message.
* @returns {{
* type: CONNECTION_FAILED,
* connection: JitsiConnection,
* error: string
* error: string,
* errorMessage: string
* }}
* @public
*/
function _connectionFailed(connection, error: string) {
export function connectionFailed(
connection: Object, error: string, errorMessage: string) {
return {
type: CONNECTION_FAILED,
connection,
error
error,
errorMessage
};
}

View File

@@ -5,7 +5,6 @@ import type { Dispatch } from 'redux';
import UIEvents from '../../../../service/UI/UIEvents';
import { SET_DOMAIN } from './actionTypes';
import './reducer';
declare var APP: Object;
declare var JitsiMeetJS: Object;
@@ -13,6 +12,11 @@ declare var JitsiMeetJS: Object;
const JitsiConferenceEvents = JitsiMeetJS.events.conference;
const logger = require('jitsi-meet-logger').getLogger(__filename);
export {
connectionEstablished,
connectionFailed
} from './actions.native.js';
/**
* Opens new connection.
*

View File

@@ -12,16 +12,16 @@ export function getDomain(stateOrGetState: Function | Object) {
= typeof stateOrGetState === 'function'
? stateOrGetState()
: stateOrGetState;
const connection = state['features/base/connection'];
const { options } = state['features/base/connection'];
let domain;
try {
domain = connection.connectionOptions.hosts.domain;
domain = options.hosts.domain;
} catch (e) {
// XXX The value of connectionOptions or any of the properties
// descending from it may be undefined at some point in the execution
// (e.g. on start). Instead of multiple checks for the undefined value,
// we just wrap it in a try-catch block.
// XXX The value of options or any of the properties descending from it
// may be undefined at some point in the execution (e.g. on start).
// Instead of multiple checks for the undefined value, we just wrap it
// in a try-catch block.
}
return domain;

View File

@@ -1,3 +1,5 @@
export * from './actions';
export * from './actionTypes';
export * from './functions';
import './reducer';

View File

@@ -69,7 +69,7 @@ function _connectionEstablished(state: Object, action: Object) {
* @private
* @returns {Object}
*/
function _constructConnectionOptions(domain: string) {
function _constructOptions(domain: string) {
// FIXME The HTTPS scheme for the BOSH URL works with meet.jit.si on both
// mobile & Web. It also works with beta.meet.jit.si on Web. Unfortunately,
// it doesn't work with beta.meet.jit.si on mobile. Temporarily, use the
@@ -96,7 +96,9 @@ function _constructConnectionOptions(domain: string) {
bosh: `${String(boshProtocol)}//${domain}/http-bind`,
hosts: {
domain,
focus: `focus.${domain}`,
// Required by:
// - lib-jitsi-meet/modules/xmpp/xmpp.js
muc: `conference.${domain}`
}
};
@@ -114,9 +116,9 @@ function _constructConnectionOptions(domain: string) {
function _setDomain(state: Object, action: Object) {
return {
...state,
connectionOptions: {
...state.connectionOptions,
..._constructConnectionOptions(action.domain)
options: {
...state.options,
..._constructOptions(action.domain)
}
};
}

View File

@@ -1,3 +1,8 @@
/* global JitsiMeetJS */
/* @flow */
// Polyfill URL for Internet Explorer.
import 'url-polyfill';
declare var JitsiMeetJS: Object;
export default JitsiMeetJS;

View File

@@ -1,3 +1,7 @@
/* @flow */
import type { Dispatch } from 'redux';
import JitsiMeetJS from './';
import {
LIB_DISPOSED,
@@ -5,8 +9,8 @@ import {
LIB_INITIALIZED,
SET_CONFIG
} from './actionTypes';
import './middleware';
import './reducer';
declare var APP: Object;
/**
* Disposes lib-jitsi-meet.
@@ -20,7 +24,7 @@ export function disposeLib() {
// there is a big chance it will be async.
// TODO Currently, lib-jitsi-meet doesn't have any functionality to
// dispose itself.
return dispatch => {
return (dispatch: Dispatch<*>) => {
dispatch({ type: LIB_DISPOSED });
return Promise.resolve();
@@ -33,7 +37,7 @@ export function disposeLib() {
* @returns {Function}
*/
export function initLib() {
return (dispatch, getState) => {
return (dispatch: Dispatch<*>, getState: Function) => {
const config = getState()['features/base/lib-jitsi-meet'].config;
if (!config) {
@@ -51,7 +55,7 @@ export function initLib() {
.catch(error => {
dispatch({
type: LIB_INIT_ERROR,
lib: { error }
error
});
// TODO Handle LIB_INIT_ERROR error somewhere instead.
@@ -67,11 +71,11 @@ export function initLib() {
* @param {Object} config - Config object accepted by JitsiMeetJS#init()
* method.
* @returns {{
* type: SET_CONFIG,
* config: Object
* }}
* type: SET_CONFIG,
* config: Object
* }}
*/
export function setConfig(config) {
export function setConfig(config: Object) {
return {
type: SET_CONFIG,
config

View File

@@ -1,5 +1,31 @@
import { loadScript } from '../../base/util';
import JitsiMeetJS from './_';
declare var APP: Object;
const JitsiConnectionErrors = JitsiMeetJS.errors.connection;
/**
* Determines whether a specific JitsiConnectionErrors instance indicates a
* fatal JitsiConnection error.
*
* FIXME Figure out the category of errors defined by the fucntion and describe
* that category. I've currently named the category fatal because it appears to
* be used in the cases of unrecoverable errors that necessitate a reload.
*
* @param {string} error - The JitsiConnectionErrors instance to
* categorize/classify.
* @returns {boolean} True if the specified JitsiConnectionErrors instance
* indicates a fatal JitsiConnection error; otherwise, false.
*/
export function isFatalJitsiConnectionError(error: string) {
return (
error === JitsiConnectionErrors.CONNECTION_DROPPED_ERROR
|| error === JitsiConnectionErrors.OTHER_ERROR
|| error === JitsiConnectionErrors.SERVER_ERROR);
}
/**
* Loads config.js file from remote server.
*
@@ -7,7 +33,7 @@ import { loadScript } from '../../base/util';
* @param {string} path='/config.js' - Relative pah to config.js file.
* @returns {Promise<Object>}
*/
export function loadConfig(host, path = '/config.js') {
export function loadConfig(host: string, path: string = '/config.js') {
// Returns config.js file from global scope. We can't use the version that's
// being used for the React Native app because the old/current Web app uses
// config from the global scope.

View File

@@ -6,3 +6,6 @@ export { JitsiMeetJS as default };
export * from './actions';
export * from './actionTypes';
export * from './functions';
import './middleware';
import './reducer';

View File

@@ -22,24 +22,44 @@ MiddlewareRegistry.register(store => next => action => {
action.participant.local && store.dispatch(disposeLib());
break;
case SET_CONFIG: {
const { dispatch, getState } = store;
const initialized
= getState()['features/base/lib-jitsi-meet'].initialized;
// XXX If we already have config, that means new config is coming, which
// means that we should dispose instance of lib initialized with
// previous config first.
// TODO Currently 'disposeLib' actually does not dispose lib-jitsi-meet.
// This functionality should be implemented.
const promise
= initialized ? dispatch(disposeLib()) : Promise.resolve();
promise.then(dispatch(initLib()));
break;
}
case SET_CONFIG:
return _setConfig(store, next, action);
}
return next(action);
});
/**
* Notifies the feature base/lib-jitsi-meet that the action SET_CONFIG is being
* dispatched within a specific Redux store.
*
* @param {Store} store - The Redux store in which the specified action is being
* dispatched.
* @param {Dispatch} next - The Redux dispatch function to dispatch the
* specified action to the specified store.
* @param {Action} action - The Redux action SET_CONFIG which is being
* dispatched in the specified store.
* @private
* @returns {Object} The new state that is the result of the reduction of the
* specified action.
*/
function _setConfig(store, next, action) {
const { dispatch, getState } = store;
const { initialized } = getState()['features/base/lib-jitsi-meet'];
// XXX Since the config is changing, the library lib-jitsi-meet must be
// initialized again with the new config. Consequntly, it may need to be
// disposed of first.
// TODO Currently, disposeLib actually does not dispose of lib-jitsi-meet
// because lib-jitsi-meet does not implement such functionality.
const disposeLIbPromise
= initialized ? dispatch(disposeLib()) : Promise.resolve();
// Let the new config into the Redux store (because initLib will read it
// from there).
const nextState = next(action);
disposeLIbPromise.then(dispatch(initLib()));
return nextState;
}

View File

@@ -1,2 +1,2 @@
require('./polyfills-browser');
require('./polyfills-browserify');
import './polyfills-browser';
import './polyfills-browserify';

View File

@@ -1,3 +1,6 @@
import BackgroundTimer from 'react-native-background-timer';
import 'url-polyfill'; // Polyfill for URL constructor
/**
* Gets the first common prototype of two specified Objects (treating the
* objects themselves as prototypes as well).
@@ -84,10 +87,6 @@ function _visitNode(node, callback) {
}
(global => {
// Polyfill for URL constructor
require('url-polyfill');
const DOMParser = require('xmldom').DOMParser;
// addEventListener
@@ -108,8 +107,8 @@ function _visitNode(node, callback) {
if (typeof global.document === 'undefined') {
const document
= new DOMParser().parseFromString(
/* source */ '<html><head></head><body></body></html>',
/* mineType */ 'text/xml');
'<html><head></head><body></body></html>',
'text/xml');
// document.addEventListener
//
@@ -141,11 +140,49 @@ function _visitNode(node, callback) {
const elementPrototype
= Object.getPrototypeOf(document.documentElement);
if (elementPrototype
&& typeof elementPrototype.querySelector === 'undefined') {
elementPrototype.querySelector = function(selectors) {
return _querySelector(this, selectors);
};
if (elementPrototype) {
if (typeof elementPrototype.querySelector === 'undefined') {
elementPrototype.querySelector = function(selectors) {
return _querySelector(this, selectors);
};
}
// Element.innerHTML
//
// Required by:
// - jQuery's .append method
if (!elementPrototype.hasOwnProperty('innerHTML')) {
Object.defineProperty(elementPrototype, 'innerHTML', {
get() {
return this.childNodes.toString();
},
set(innerHTML) {
// MDN says: removes all of element's children, parses
// the content string and assigns the resulting nodes as
// children of the element.
// Remove all of element's children.
this.textContent = '';
// Parse the content string.
const d
= new DOMParser().parseFromString(
`<div>${innerHTML}</div>`,
'text/xml');
// Assign the resulting nodes as children of the
// element.
const documentElement = d.documentElement;
let child;
// eslint-disable-next-line no-cond-assign
while (child = documentElement.firstChild) {
this.appendChild(child);
}
}
});
}
}
// FIXME There is a weird infinite loop related to console.log and
@@ -294,17 +331,17 @@ function _visitNode(node, callback) {
//
// Required by:
// - Strophe
if (prototype && typeof prototype.responseXML === 'undefined') {
if (prototype && !prototype.hasOwnProperty('responseXML')) {
Object.defineProperty(prototype, 'responseXML', {
configurable: true,
enumerable: true,
get() {
const responseText = this.responseText;
let responseXML;
if (responseText) {
responseXML = new DOMParser()
.parseFromString(responseText);
responseXML
= new DOMParser().parseFromString(
responseText,
'text/xml');
}
return responseXML;
@@ -313,4 +350,18 @@ function _visitNode(node, callback) {
}
}
// Timers
//
// React Native's timers won't run while the app is in the background, this
// is a known limitation. Replace them with a background-friendly
// alternative.
//
// Required by:
// - lib-jitsi-meet
// - Strophe
global.clearTimeout = window.clearTimeout = BackgroundTimer.clearTimeout;
global.clearInterval = window.clearInterval = BackgroundTimer.clearInterval;
global.setInterval = window.setInterval = BackgroundTimer.setInterval;
global.setTimeout = window.setTimeout = BackgroundTimer.setTimeout;
})(global || window || this); // eslint-disable-line no-invalid-this

View File

@@ -8,7 +8,7 @@ import {
} from './actionTypes';
/**
* Initial state of 'features/base/lib-jitsi-meet'.
* The initial state of 'features/base/lib-jitsi-meet'.
*
* @type {{
* initializationError: null,
@@ -16,6 +16,14 @@ import {
* }}
*/
const INITIAL_STATE = {
/**
* The mandatory configuration to be passed to JitsiMeetJS#init(). The app
* will download config.js from the Jitsi Meet deployment and taks its
* values into account but the values bellow will be enforced (because they
* are essential to the correct execution of the application).
*
* @type {Object}
*/
config: {
// FIXME The support for audio levels in lib-jitsi-meet polls the
// statistics of WebRTC at a short interval multiple times a second.
@@ -31,7 +39,8 @@ const INITIAL_STATE = {
// place). Fortunately, these pieces of JavaScript currently involve
// third parties and we can temporarily disable them (until we implement
// an alternative to async script elements on React Native).
disableThirdPartyRequests: true
disableThirdPartyRequests: true,
preferH264: true
},
initializationError: null,
initialized: false
@@ -47,7 +56,7 @@ ReducerRegistry.register(
case LIB_INIT_ERROR:
return {
...state,
initializationError: action.lib.error,
initializationError: action.error,
initialized: false
};
@@ -80,10 +89,12 @@ function _setConfig(state, action) {
return {
...state,
config: {
// The final config is the result of augmenting the default config
// with whatever the deployment has chosen to override/overwrite.
...INITIAL_STATE.config,
...action.config
...action.config,
// The config of INITIAL_STATE is meant to override the config
// downloaded from the Jitsi Meet deployment because the former
// contains values that are mandatory.
...INITIAL_STATE.config
}
};
}

View File

@@ -8,8 +8,6 @@ import {
SET_VIDEO_MUTED
} from './actionTypes';
import { CAMERA_FACING_MODE } from './constants';
import './middleware';
import './reducer';
/**
* Action to set the muted state of the local audio.

View File

@@ -3,3 +3,6 @@ export * from './actionTypes';
export * from './components';
export * from './constants';
export * from './functions';
import './middleware';
import './reducer';

View File

@@ -7,8 +7,6 @@ import {
PIN_PARTICIPANT
} from './actionTypes';
import { getLocalParticipant } from './functions';
import './middleware';
import './reducer';
/**
* Action to update a participant's email.

View File

@@ -2,3 +2,6 @@ export * from './actions';
export * from './actionTypes';
export * from './constants';
export * from './functions';
import './middleware';
import './reducer';

View File

@@ -1,6 +1,5 @@
import React from 'react';
import {
Dimensions,
TouchableHighlight,
TouchableWithoutFeedback,
View
@@ -32,23 +31,8 @@ export class Container extends AbstractContainer {
let { onClick, style, touchFeedback, visible, ...props } = this.props;
// visible
// The following property is responsible to hide/show this Container by
// moving it out of site of the screen boundaries. An attempt to use the
// opacity property was made in order to eventually implement a
// fadeIn/fadeOut animation, however a known React Native problem was
// discovered, which allows the view to still capture touch events even
// if hidden.
// TODO Alternatives will be investigated.
let parentStyle;
if (typeof visible !== 'undefined' && !visible) {
const windowDimensions = Dimensions.get('window');
parentStyle = {
bottom: -windowDimensions.height,
right: -windowDimensions.width
};
return null;
}
// onClick & touchFeedback
@@ -56,13 +40,6 @@ export class Container extends AbstractContainer {
const renderParent = touchFeedback || onClick;
if (!renderParent && parentStyle) {
style = {
...style,
...parentStyle
};
}
// eslint-disable-next-line object-property-newline
let component = this._render(View, { ...props, style });
@@ -72,7 +49,6 @@ export class Container extends AbstractContainer {
const parentProps = {};
onClick && (parentProps.onPress = onClick);
parentStyle && (parentProps.style = parentStyle);
component = React.createElement(parentType, parentProps, component);
}

View File

@@ -1,24 +0,0 @@
import { shimStyles } from './shimStyles';
/**
* Create a style sheet using the provided style definitions.
*
* @param {Object} styles - A dictionary of named style definitions.
* @param {Object} [overrides={}] - Optional set of additional (often
* platform-dependent/specific) style definitions that will override the base
* (often platform-independent) styles.
* @returns {Object}
*/
export function createStyleSheet(styles, overrides = {}) {
const combinedStyles = {};
for (const k of Object.keys(styles)) {
combinedStyles[k]
= shimStyles({
...styles[k],
...overrides[k]
});
}
return combinedStyles;
}

View File

@@ -0,0 +1,96 @@
/* @flow */
import { Platform } from '../react';
import { ColorPalette } from './components';
declare type StyleSheet = Object;
/**
* The list of the well-known style properties which may not be numbers on Web
* but must be numbers on React Native.
*
* @private
*/
const _WELL_KNOWN_NUMBER_PROPERTIES = [ 'height', 'width' ];
/* eslint-disable flowtype/space-before-type-colon */
/**
* Create a style sheet using the provided style definitions.
*
* @param {StyleSheet} styles - A dictionary of named style definitions.
* @param {StyleSheet} [overrides={}] - Optional set of additional (often
* platform-dependent/specific) style definitions that will override the base
* (often platform-independent) styles.
* @returns {StyleSheet}
*/
export function createStyleSheet(styles: StyleSheet, overrides: StyleSheet = {})
: StyleSheet {
/* eslint-enable flowtype/space-before-type-colon */
const combinedStyles = {};
for (const k of Object.keys(styles)) {
combinedStyles[k]
= _shimStyles({
...styles[k],
...overrides[k]
});
}
return combinedStyles;
}
/**
* Works around a bug in react-native or react-native-webrtc on Android which
* causes Views overlaying RTCView to be clipped. Even though we (may) display
* multiple RTCViews, it is enough to apply the fix only to a View with a
* bounding rectangle containing all RTCviews and their overlaying Views.
*
* @param {StyleSheet} styles - An object which represents a stylesheet.
* @public
* @returns {StyleSheet}
*/
export function fixAndroidViewClipping<T: StyleSheet>(styles: T): T {
if (Platform.OS === 'android') {
styles.borderColor = ColorPalette.appBackground;
styles.borderWidth = 0.2;
}
return styles;
}
/**
* Shims style properties to work correctly on native. Allows us to minimize the
* number of style declarations that need to be set or overridden for specific
* platforms.
*
* @param {StyleSheet} styles - An object which represents a stylesheet.
* @private
* @returns {StyleSheet}
*/
function _shimStyles<T: StyleSheet>(styles: T): T {
// Certain style properties may not be numbers on Web but must be numbers on
// React Native. For example, height and width may be expressed in percent
// on Web but React Native will not understand them and we will get errors
// (at least during development). Convert such well-known properties to
// numbers if possible; otherwise, remove them to avoid runtime errors.
for (const k of _WELL_KNOWN_NUMBER_PROPERTIES) {
const v = styles[k];
const typeofV = typeof v;
if (typeofV !== 'undefined' && typeofV !== 'number') {
const numberV = Number(v);
if (Number.isNaN(numberV)) {
delete styles[k];
} else {
styles[k] = numberV;
}
}
}
return styles;
}

View File

@@ -1,2 +1,2 @@
export * from './components';
export * from './createStyleSheet';
export * from './functions';

View File

@@ -1,38 +0,0 @@
/**
* The list of the well-known style properties which may not be numbers on Web
* but must be numbers on React Native.
*/
const WELL_KNOWN_NUMBER_PROPERTIES = [ 'height', 'width' ];
/**
* Shim style properties to work correctly on native.
*
* Using this shimStyles method allows us to minimize the number of style
* declarations that need to be set or overridden for specific platforms.
*
* @param {Object} styles - A dictionary of named style definitions.
* @returns {Object}
*/
export function shimStyles(styles) {
// Certain style properties may not be numbers on Web but must be numbers on
// React Native. For example, height and width may be expressed in percent
// on Web but React Native will not understand them and we will get errors
// (at least during development). Convert such well-known properties to
// numbers if possible; otherwise, remove them to avoid runtime errors.
for (const k of WELL_KNOWN_NUMBER_PROPERTIES) {
const v = styles[k];
const typeofV = typeof v;
if (typeofV !== 'undefined' && typeofV !== 'number') {
const numberV = Number(v);
if (Number.isNaN(numberV)) {
delete styles[k];
} else {
styles[k] = numberV;
}
}
}
return styles;
}

View File

@@ -10,8 +10,6 @@ import {
TRACK_REMOVED,
TRACK_UPDATED
} from './actionTypes';
import './middleware';
import './reducer';
const JitsiTrackErrors = JitsiMeetJS.errors.track;
const JitsiTrackEvents = JitsiMeetJS.events.track;

View File

@@ -1,3 +1,6 @@
export * from './actions';
export * from './actionTypes';
export * from './functions';
import './middleware';
import './reducer';

View File

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

View File

@@ -6,6 +6,7 @@ import { connect as reactReduxConnect } from 'react-redux';
import { connect, disconnect } from '../../base/connection';
import { Watermarks } from '../../base/react';
import { FeedbackButton } from '../../feedback';
import { OverlayContainer } from '../../overlay';
/**
* For legacy reasons, inline style for display none.
@@ -162,6 +163,8 @@ class Conference extends Component {
</div>
</div>
</div>
<OverlayContainer />
</div>
);
}

View File

@@ -1,4 +1,8 @@
import { ColorPalette, createStyleSheet } from '../../base/styles';
import {
ColorPalette,
createStyleSheet,
fixAndroidViewClipping
} from '../../base/styles';
/**
* The style of the conference UI (component).
@@ -15,11 +19,11 @@ export const styles = createStyleSheet({
/**
* Conference style.
*/
conference: {
conference: fixAndroidViewClipping({
alignSelf: 'stretch',
backgroundColor: ColorPalette.appBackground,
flex: 1
},
}),
/**
* ParticipantView style

View File

@@ -3,6 +3,7 @@
import { StatusBar } from 'react-native';
import { Immersive } from 'react-native-immersive';
import { APP_STATE_CHANGED } from '../background';
import {
CONFERENCE_FAILED,
CONFERENCE_LEFT,
@@ -23,13 +24,25 @@ import { MiddlewareRegistry } from '../base/redux';
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
let fullScreen;
let fullScreen = null;
switch (action.type) {
case CONFERENCE_WILL_JOIN: {
const conference = store.getState()['features/base/conference'];
case APP_STATE_CHANGED: {
// Check if we just came back from the background and reenable full
// screen mode if necessary.
if (action.appState === 'active') {
const { audioOnly, conference }
= store.getState()['features/base/conference'];
fullScreen = !conference.audioOnly;
fullScreen = conference ? !audioOnly : false;
}
break;
}
case CONFERENCE_WILL_JOIN: {
const { audioOnly } = store.getState()['features/base/conference'];
fullScreen = !audioOnly;
break;
}
@@ -37,10 +50,6 @@ MiddlewareRegistry.register(store => next => action => {
case CONFERENCE_LEFT:
fullScreen = false;
break;
default:
fullScreen = null;
break;
}
if (fullScreen !== null) {

View File

@@ -6,8 +6,6 @@ import {
} from '../base/tracks';
import { SELECT_LARGE_VIDEO_PARTICIPANT } from './actionTypes';
import './middleware';
import './reducer';
/**
* Signals conference to select a participant.

View File

@@ -1,2 +1,5 @@
export * from './actions';
export * from './components';
import './middleware';
import './reducer';

View File

@@ -0,0 +1,25 @@
import { Symbol } from '../base/react';
/**
* The type of the Redux action which signals that the prompt for media
* permission is visible or not.
*
* {
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
* isVisible: {boolean},
* browser: {string}
* }
* @public
*/
export const MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED
= Symbol('MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED');
/**
* The type of the Redux action which signals that a suspend was detected.
*
* {
* type: SUSPEND_DETECTED
* }
* @public
*/
export const SUSPEND_DETECTED = Symbol('SUSPEND_DETECTED');

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