Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f50a31b4e8 | ||
|
|
b226c3aca3 | ||
|
|
4979666a89 | ||
|
|
c9636f85b9 | ||
|
|
436bc87a86 | ||
|
|
e89c2b242d | ||
|
|
02b26a65bb | ||
|
|
6a3e4bb59f | ||
|
|
b01ad360da | ||
|
|
c7f3740099 | ||
|
|
554595acd7 | ||
|
|
ee4ddd5446 | ||
|
|
ebab617a12 | ||
|
|
bc5d92a452 | ||
|
|
2f388dfb6a | ||
|
|
73a0197eb2 | ||
|
|
b6990e9e5d | ||
|
|
26e119bfc2 | ||
|
|
023359b9d2 | ||
|
|
2128d047e1 | ||
|
|
a89349c5b9 | ||
|
|
d109b8beb6 | ||
|
|
9b40572921 | ||
|
|
aaf7a38cce | ||
|
|
1ed0759a50 | ||
|
|
5b6985fc5c | ||
|
|
538af01bf5 | ||
|
|
92d0589a37 | ||
|
|
f3269070b2 | ||
|
|
d93bd3eda7 | ||
|
|
0dbbc5d8b6 | ||
|
|
08efd5ecab | ||
|
|
dba1bcb0e3 | ||
|
|
348403abff | ||
|
|
c290cf45b7 | ||
|
|
175c8e6e50 | ||
|
|
f90667b23c | ||
|
|
cf69d591e4 | ||
|
|
e599491583 | ||
|
|
d1520773cf | ||
|
|
573ca97b6c | ||
|
|
0d97f14a1a | ||
|
|
b8f28abfdf | ||
|
|
9ac7c97e67 | ||
|
|
52b3eaacb5 | ||
|
|
9b01ae6db9 | ||
|
|
469487ad36 | ||
|
|
176c3c1601 | ||
|
|
94391234cc | ||
|
|
d84901f196 | ||
|
|
c6b117565d | ||
|
|
2a9124acb5 | ||
|
|
401a783d6a | ||
|
|
39483a30b6 | ||
|
|
0e2a07f8d7 | ||
|
|
36f5b0218d | ||
|
|
a1b3c56de7 | ||
|
|
6d664f133e | ||
|
|
732a433ec1 | ||
|
|
f7dcd1ba2c | ||
|
|
55a8b44224 | ||
|
|
e29db31d91 | ||
|
|
183d3c3ca4 | ||
|
|
c57e713696 | ||
|
|
4519f26adf | ||
|
|
c26f9cc01f | ||
|
|
f6f730b994 |
@@ -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')
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 38 KiB |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
200
css/_mixins.scss
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
html, body {
|
||||
width: 100%;
|
||||
height:100%;
|
||||
color: $defaultColor;
|
||||
background: $defaultBackground;
|
||||
}
|
||||
|
||||
.redirectPageMessage {
|
||||
width: 30%;
|
||||
margin: 20% auto;
|
||||
|
||||
@@ -16,7 +16,7 @@ $defaultToolbarSize: 50px;
|
||||
$thumbnailToolbarHeight: 22px;
|
||||
$thumbnailIndicatorBorder: 2px;
|
||||
$thumbnailIndicatorSize: $thumbnailToolbarHeight;
|
||||
$thumbnailVideoMargin: 5px;
|
||||
$thumbnailVideoMargin: 2px;
|
||||
$thumbnailsBorder: 2px;
|
||||
$thumbnailVideoBorder: 2px;
|
||||
$hideFilmstripButtonWidth: 17px;
|
||||
|
||||
41
css/components/_input-slider.scss
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ $baseLight: #FFFFFF;
|
||||
*/
|
||||
$controlBackground: $baseLight;
|
||||
$controlColor: #333333;
|
||||
$sliderTrackBackground: #474747;
|
||||
$sliderThumbBackground: #3572b0;
|
||||
|
||||
/**
|
||||
* Buttons
|
||||
|
||||
2
debian/jitsi-meet-tokens.install
vendored
@@ -1 +1 @@
|
||||
prosody-plugins/ /usr/share/jitsi-meet/
|
||||
resources/prosody-plugins/ /usr/share/jitsi-meet/
|
||||
|
||||
@@ -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
|
||||
|
||||
|
Before Width: | Height: | Size: 234 B |
|
Before Width: | Height: | Size: 611 B |
@@ -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"
|
||||
};
|
||||
|
||||
BIN
ios/app/Images.xcassets/AppIcon.appiconset/AppIcon-76@1x.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
ios/app/Images.xcassets/AppIcon.appiconset/AppIcon-76@2x.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
ios/app/Images.xcassets/AppIcon.appiconset/AppIcon-83.5@2x.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
ios/app/Images.xcassets/LaunchScreen.imageset/LaunchScreen-480@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 270 KiB |
@@ -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/>
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
|
||||
@@ -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
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"en": "",
|
||||
"bg": "",
|
||||
"de": "",
|
||||
"es": "",
|
||||
"fr": "",
|
||||
"hy": "",
|
||||
"it": "",
|
||||
"oc": "",
|
||||
"pl": "",
|
||||
"ptBR": "",
|
||||
"ru": "",
|
||||
"sk": "",
|
||||
"sl": "",
|
||||
"sv": "",
|
||||
"tr": ""
|
||||
}
|
||||
@@ -13,5 +13,6 @@
|
||||
"sk": "Slovak",
|
||||
"sl": "Slovenian",
|
||||
"sv": "Swedish",
|
||||
"tr": "Turkish"
|
||||
"tr": "Turkish",
|
||||
"zhCN": "Chinese (China)"
|
||||
}
|
||||
|
||||
@@ -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
@@ -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": ""
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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},
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -2,3 +2,5 @@ export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
export * from './functions';
|
||||
|
||||
import './reducer';
|
||||
|
||||
43
react/features/background/actionTypes.js
Normal 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');
|
||||
81
react/features/background/actions.js
Normal 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));
|
||||
};
|
||||
}
|
||||
5
react/features/background/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
109
react/features/background/middleware.js
Normal 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));
|
||||
}
|
||||
31
react/features/background/reducer.js
Normal 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;
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './functions';
|
||||
|
||||
import './reducer';
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/* global JitsiMeetJS */
|
||||
/* @flow */
|
||||
|
||||
// Polyfill URL for Internet Explorer.
|
||||
import 'url-polyfill';
|
||||
|
||||
declare var JitsiMeetJS: Object;
|
||||
|
||||
export default JitsiMeetJS;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -6,3 +6,6 @@ export { JitsiMeetJS as default };
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
require('./polyfills-browser');
|
||||
require('./polyfills-browserify');
|
||||
import './polyfills-browser';
|
||||
import './polyfills-browserify';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -3,3 +3,6 @@ export * from './actionTypes';
|
||||
export * from './components';
|
||||
export * from './constants';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
||||
@@ -7,8 +7,6 @@ import {
|
||||
PIN_PARTICIPANT
|
||||
} from './actionTypes';
|
||||
import { getLocalParticipant } from './functions';
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
||||
/**
|
||||
* Action to update a participant's email.
|
||||
|
||||
@@ -2,3 +2,6 @@ export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './constants';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
96
react/features/base/styles/functions.js
Normal 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;
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './components';
|
||||
export * from './createStyleSheet';
|
||||
export * from './functions';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './interceptComponent';
|
||||
export * from './loadScript';
|
||||
export * from './randomUtil';
|
||||
export * from './roomnameGenerator';
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
export * from './actions';
|
||||
export * from './components';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
||||
25
react/features/overlay/actionTypes.js
Normal 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');
|
||||
39
react/features/overlay/actions.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
SUSPEND_DETECTED
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Signals that the prompt for media permission is visible or not.
|
||||
*
|
||||
* @param {boolean} isVisible - If the value is true - the prompt for media
|
||||
* permission is visible otherwise the value is false/undefined.
|
||||
* @param {string} browser - The name of the current browser.
|
||||
* @returns {{
|
||||
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
* browser: {string},
|
||||
* isVisible: {boolean}
|
||||
* }}
|
||||
* @public
|
||||
*/
|
||||
export function mediaPermissionPromptVisibilityChanged(isVisible, browser) {
|
||||
return {
|
||||
type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
browser,
|
||||
isVisible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that suspend was detected.
|
||||
*
|
||||
* @returns {{
|
||||
* type: SUSPEND_DETECTED
|
||||
* }}
|
||||
* @public
|
||||
*/
|
||||
export function suspendDetected() {
|
||||
return {
|
||||
type: SUSPEND_DETECTED
|
||||
};
|
||||
}
|
||||