Compare commits

...

32 Commits

Author SHA1 Message Date
damencho
68384dcb3c Ignore errors when restarting services.
Sometimes conflicting or wrong configuration can leave the package in broken state and users cannot even uninstall/purge the packages, and it also breaks any other package installation.
2019-12-02 13:33:35 +00:00
damencho
f29632a224 Handle upgrades after moving config files to /etc. 2019-12-02 12:30:09 +00:00
damencho
5d30e007b4 Moves interface_config and logging_config to /etc folder. 2019-12-02 12:08:10 +00:00
damencho
965106e66f Moves config template files out of doc folder. 2019-12-02 11:12:09 +00:00
Hristo Terezov
1aaaae24ee feat(Amplitude): enable referrer 2019-11-29 15:43:14 +00:00
Hristo Terezov
9191000da4 chore(package.json): Update lib-jitsi-meet 2019-11-29 13:41:14 +00:00
Bettenbuk Zoltan
8eb93086bd fix: set an avatar icon size relative to the container 2019-11-29 14:37:35 +01:00
Bettenbuk Zoltan
b64294af6d fix: emoji in links 2019-11-29 14:36:42 +01:00
Hristo Terezov
bbf33a8895 feat(welcome-page): Add viewed analytics event. 2019-11-28 15:36:12 +00:00
Jaya Allamsetty
bcc1289a23 feat(presenter): Modify the default behavior for presenter mode, it set to off when screensharing is turned on. Also, revert video to the original state when screensharing is turned off. 2019-11-27 11:13:36 -08:00
Saúl Ibarra Corretgé
58bd48c1ae android: disable ConnectionService if permissions are not granted
Some devices seem to have a bug in their Android versions and startCall fails
with SecurityError because the CALL_PHONE permissions is not granted. This is
not a requirement for self-managed connection services as per the official
documentation though:
https://developer.android.com/guide/topics/connectivity/telecom/selfManaged

Alas, connection services takes over audio device management too, so let's
handle the error and disable CS if we get SecurityError.
2019-11-27 14:33:25 +01:00
Saúl Ibarra Corretgé
1a3736bf98 android: unregister phone account if startCall fails 2019-11-27 14:33:25 +01:00
Saúl Ibarra Corretgé
0eec182df4 android: remove old code for accepting SDK license
It can now be automated in a CI environment as follows:

yes | sdkmanager --licenses
2019-11-27 14:24:29 +01:00
Saúl Ibarra Corretgé
c526844eb2 chore: remove unused images 2019-11-26 21:20:50 +01:00
Saúl Ibarra Corretgé
d856c1f328 ios: add apple-touch-icon icon
Ref: https://webhint.io/docs/user-guide/hints/hint-apple-touch-icons/
2019-11-26 21:13:02 +01:00
Saúl Ibarra Corretgé
15e47a9eb3 android: update native dependencies 2019-11-26 20:33:38 +01:00
Saúl Ibarra Corretgé
da98d39b61 doc: add app download badges to README 2019-11-26 14:58:35 +01:00
Bettenbuk Zoltan
411bafb5a6 feat: minimized bottom menu 2019-11-26 12:08:43 +01:00
Jaya Allamsetty
0a64bf2068 feat(presenter): add Presenter Mode
- Adds the ability to share video as a "PiP" when screenshare is in progress.
- Add a method for creating a local presenter track.
- Make sure isLocalVideoTrackMuted returns the correct mute state when only screenshare is present.
- Make sure we get the updated window size of the window being shared before painting it on the canvas.
- Make sure we check if the shared window has been resized
2019-11-26 11:57:03 +01:00
damencho
db6a2673de Handles unique Id for a meeting. 2019-11-26 10:37:19 +00:00
damencho
e11d4d3101 Installs prosody plugins with jitsi-meet-prosody package. 2019-11-26 10:37:19 +00:00
Saúl Ibarra Corretgé
8fd3bb2302 android: fallbacck to speaker in ConnectionService handler
It has been our default for a while.
2019-11-26 11:30:18 +01:00
theunafraid
fb3a832a52 Add shortcut key for toggle tile view (#4882)
* Add shortcut key for toggle tile view

* Toggle tile view shortcut - undo main-enGB.json

* Add analytics

* Use already defined toolbar translations
2019-11-22 16:15:39 +00:00
Saúl Ibarra Corretgé
9c146c1245 subject: hide participant count for 1-1 calls
refs: https://github.com/jitsi/jitsi-meet/issues/4871
2019-11-22 10:49:24 +01:00
Saúl Ibarra Corretgé
792f506425 ios: drop support for iOS 10 2019-11-22 10:46:02 +01:00
Bettenbuk Zoltan
6121e9fc65 feat: improve chat UX 2019-11-21 18:11:58 +01:00
Bettenbuk Zoltan
955fa1f49f fix: undefined is not an object on bitrate 2019-11-21 18:11:58 +01:00
damencho
2544d0a084 Fixes the message for who kicked you. 2019-11-20 17:01:00 +02:00
Bettenbuk Zoltan
8f0a12016a fix: return room lock conference, when there is no other 2019-11-20 13:28:47 +01:00
Leonard Kim
34ccd3524f fix(chat): preserve intentional linebreaks in message display 2019-11-20 08:58:02 +01:00
Leonard Kim
563e99ecd3 fix(chat): wrap long text 2019-11-18 09:31:47 -08:00
Leonard Kim
70f14be50f fix(large-video): center dominant speaker avatar using css
The vertical alignment was being set with javascript.
Recent changes might make the setting of alignment exit
early due to height 0 video. As position can be set
declaratively with css, use css to set position.
2019-11-15 07:51:59 -08:00
94 changed files with 1143 additions and 686 deletions

View File

@@ -29,11 +29,18 @@ You can download source archives (produced by ```make source-package```):
### Mobile apps
You can get our mobile versions from here:
* [Android](https://play.google.com/store/apps/details?id=org.jitsi.meet)
[<img src="resources/img/google-play-badge.png" height="50">](https://play.google.com/store/apps/details?id=org.jitsi.meet)
* [Android (F-Droid)](https://f-droid.org/en/packages/org.jitsi.meet/)
[<img src="resources/img/f-droid-badge.png" height="50">](https://f-droid.org/en/packages/org.jitsi.meet/)
* [iOS](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
[<img src="resources/img/appstore-badge.png" height="50">](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
You can also sign up for our open beta testing here:
* [Android](https://play.google.com/apps/testing/org.jitsi.meet)

View File

@@ -69,7 +69,9 @@ repositories {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.appcompat:appcompat:1.1.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-5'
if (!rootProject.ext.libreBuild) {
implementation 'com.google.android.gms:play-services-auth:16.0.1'
@@ -83,9 +85,6 @@ dependencies {
}
implementation project(':sdk')
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
}
gradle.projectsEvaluated {

View File

@@ -5,7 +5,6 @@
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name=".MainApplication"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme">
<activity

View File

@@ -1,36 +0,0 @@
/*
* Copyright @ 2018-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet;
import android.app.Application;
import com.squareup.leakcanary.LeakCanary;
/**
* Simple {@link Application} for hooking up LeakCanary:
* https://github.com/square/leakcanary
*/
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (!LeakCanary.isInAnalyzerProcess(this)) {
LeakCanary.install(this);
}
}
}

View File

@@ -13,8 +13,8 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.2'
classpath 'com.google.gms:google-services:4.2.0'
classpath 'io.fabric.tools:gradle:1.27.0'
classpath 'com.google.gms:google-services:4.3.3'
classpath 'io.fabric.tools:gradle:1.28.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files.
@@ -165,50 +165,6 @@ ext {
libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean()
}
// If Android SDK is not installed, accept its license so that it
// is automatically downloaded.
afterEvaluate { project ->
// Either the environment variable ANDROID_HOME or the property sdk.dir in
// local.properties identifies where Android SDK is installed.
def androidHome = System.env.ANDROID_HOME
if (!androidHome) {
// ANDROID_HOME is not set. Is sdk.dir set?
def file = file("${project.rootDir}/local.properties")
def props = new Properties()
if (file.canRead()) {
file.withInputStream {
props.load(it)
androidHome = props.'sdk.dir'
}
}
if (!androidHome && (!file.exists() || file.canWrite())) {
// Neither ANDROID_HOME nor sdk.dir is set. Set sdk.dir (because
// environment variables cannot be set).
props.'sdk.dir' = "${project.buildDir}/android-sdk".toString()
file.withOutputStream {
props.store(it, null)
androidHome = props.'sdk.dir'
}
}
}
// If the license is not accepted, accept it so that automatic downloading
// kicks in.
// The license hash can be taken from the accepted licenses, by doing this
// on your local machine the file is
// ${androidHome}/licenses/android-sdk-license
if (androidHome) {
def dir = file("${androidHome}/licenses")
dir.mkdirs()
def file = file("${dir.path}/android-sdk-license")
if (!file.exists()) {
file.withWriter {
def hash = 'd56f5187479451eabf01fb78af6dfcb131a6481e'
it.write(hash, 0, hash.length())
}
}
}
}
// Force the version of the Android build tools we have chosen on all
// subprojects. The forcing was introduced for react-native and the third-party
// modules that we utilize such as react-native-background-timer.

View File

@@ -37,10 +37,12 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.fragment:fragment:1.0.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.fragment:fragment:1.1.0'
//noinspection GradleDynamicVersion
api 'com.facebook.react:react-native:+'
//noinspection GradleDynamicVersion
implementation 'org.webkit:android-jsc:+'
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'

View File

@@ -53,7 +53,7 @@ class AudioDeviceHandlerConnectionService implements
*/
private static int audioDeviceToRouteInt(String audioDevice) {
if (audioDevice == null) {
return CallAudioState.ROUTE_EARPIECE;
return CallAudioState.ROUTE_SPEAKER;
}
switch (audioDevice) {
case AudioModeModule.DEVICE_BLUETOOTH:
@@ -66,7 +66,7 @@ class AudioDeviceHandlerConnectionService implements
return CallAudioState.ROUTE_SPEAKER;
default:
JitsiMeetLogger.e(TAG + " Unsupported device name: " + audioDevice);
return CallAudioState.ROUTE_EARPIECE;
return CallAudioState.ROUTE_SPEAKER;
}
}

View File

@@ -105,15 +105,22 @@ class RNConnectionService extends ReactContextBaseJavaModule {
ConnectionService.registerStartCallPromise(callUUID, promise);
try {
TelecomManager tm
= (TelecomManager) ctx.getSystemService(
Context.TELECOM_SERVICE);
TelecomManager tm = null;
try {
tm = (TelecomManager) ctx.getSystemService(Context.TELECOM_SERVICE);
tm.placeCall(address, extras);
} catch (Exception e) {
JitsiMeetLogger.e(e, TAG + " error in startCall");
if (tm != null) {
tm.unregisterPhoneAccount(accountHandle);
}
ConnectionService.unregisterStartCallPromise(callUUID);
promise.reject(e);
if (e instanceof SecurityException) {
promise.reject("SECURITY_ERROR", "Required permissions not granted.");
} else {
promise.reject(e);
}
}
}

View File

@@ -93,10 +93,15 @@ import {
participantRoleChanged,
participantUpdated
} from './react/features/base/participants';
import { updateSettings } from './react/features/base/settings';
import {
getUserSelectedCameraDeviceId,
updateSettings
} from './react/features/base/settings';
import {
createLocalPresenterTrack,
createLocalTracksF,
destroyLocalTracks,
isLocalVideoTrackMuted,
isLocalTrackMuted,
isUserInteractionRequiredForUnmute,
replaceLocalTrack,
@@ -113,6 +118,7 @@ import {
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay';
import { suspendDetected } from './react/features/power-monitor';
import { setSharedVideoStatus } from './react/features/shared-video';
import { createPresenterEffect } from './react/features/stream-effects/presenter';
import { endpointMessageReceived } from './react/features/subtitles';
const logger = require('jitsi-meet-logger').getLogger(__filename);
@@ -437,6 +443,11 @@ export default {
*/
localAudio: null,
/**
* The local presenter video track (if any).
*/
localPresenterVideo: null,
/**
* The local video track (if any).
* FIXME tracks from redux store should be the single source of truth, but
@@ -722,9 +733,8 @@ export default {
isLocalVideoMuted() {
// If the tracks are not ready, read from base/media state
return this._localTracksInitialized
? isLocalTrackMuted(
APP.store.getState()['features/base/tracks'],
MEDIA_TYPE.VIDEO)
? isLocalVideoTrackMuted(
APP.store.getState()['features/base/tracks'])
: isVideoMutedByUser(APP.store);
},
@@ -798,6 +808,51 @@ export default {
this.muteAudio(!this.isLocalAudioMuted(), showUI);
},
/**
* Simulates toolbar button click for presenter video mute. Used by
* shortcuts and API.
* @param mute true for mute and false for unmute.
* @param {boolean} [showUI] when set to false will not display any error
* dialogs in case of media permissions error.
*/
async mutePresenterVideo(mute, showUI = true) {
const maybeShowErrorDialog = error => {
showUI && APP.store.dispatch(notifyCameraError(error));
};
if (mute) {
try {
await this.localVideo.setEffect(undefined);
APP.store.dispatch(
setVideoMuted(mute, MEDIA_TYPE.PRESENTER));
} catch (err) {
logger.error('Failed to mute the Presenter video');
}
return;
}
const { height } = this.localVideo.track.getSettings();
const defaultCamera
= getUserSelectedCameraDeviceId(APP.store.getState());
let effect;
try {
effect = await this._createPresenterStreamEffect(height,
defaultCamera);
} catch (err) {
logger.error('Failed to unmute Presenter Video');
maybeShowErrorDialog(err);
return;
}
try {
await this.localVideo.setEffect(effect);
APP.store.dispatch(setVideoMuted(mute, MEDIA_TYPE.PRESENTER));
} catch (err) {
logger.error('Failed to apply the Presenter effect', err);
}
},
/**
* Simulates toolbar button click for video mute. Used by shortcuts and API.
* @param mute true for mute and false for unmute.
@@ -812,6 +867,10 @@ export default {
return;
}
if (this.isSharingScreen) {
return this.mutePresenterVideo(mute);
}
// If not ready to modify track's state yet adjust the base/media
if (!this._localTracksInitialized) {
// This will only modify base/media.video.muted which is then synced
@@ -1351,7 +1410,7 @@ export default {
* in case it fails.
* @private
*/
_turnScreenSharingOff(didHaveVideo, wasVideoMuted) {
_turnScreenSharingOff(didHaveVideo) {
this._untoggleScreenSharing = null;
this.videoSwitchInProgress = true;
const { receiver } = APP.remoteControl;
@@ -1369,13 +1428,7 @@ export default {
.then(([ stream ]) => this.useVideoStream(stream))
.then(() => {
sendAnalytics(createScreenSharingEvent('stopped'));
logger.log('Screen sharing stopped, switching to video.');
if (!this.localVideo && wasVideoMuted) {
return Promise.reject('No local video to be muted!');
} else if (wasVideoMuted && this.localVideo) {
return this.localVideo.mute();
}
logger.log('Screen sharing stopped.');
})
.catch(error => {
logger.error('failed to switch back to local video', error);
@@ -1390,6 +1443,16 @@ export default {
promise = this.useVideoStream(null);
}
// mute the presenter track if it exists.
if (this.localPresenterVideo) {
APP.store.dispatch(
setVideoMuted(true, MEDIA_TYPE.PRESENTER));
this.localPresenterVideo.dispose();
APP.store.dispatch(
trackRemoved(this.localPresenterVideo));
this.localPresenterVideo = null;
}
return promise.then(
() => {
this.videoSwitchInProgress = false;
@@ -1415,7 +1478,7 @@ export default {
* 'window', etc.).
* @return {Promise.<T>}
*/
toggleScreenSharing(toggle = !this._untoggleScreenSharing, options = {}) {
async toggleScreenSharing(toggle = !this._untoggleScreenSharing, options = {}) {
if (this.videoSwitchInProgress) {
return Promise.reject('Switch in progress.');
}
@@ -1429,7 +1492,15 @@ export default {
}
if (toggle) {
return this._switchToScreenSharing(options);
try {
await this._switchToScreenSharing(options);
return;
} catch (err) {
logger.error('Failed to switch to screensharing', err);
return;
}
}
return this._untoggleScreenSharing
@@ -1454,8 +1525,7 @@ export default {
_createDesktopTrack(options = {}) {
let externalInstallation = false;
let DSExternalInstallationInProgress = false;
const didHaveVideo = Boolean(this.localVideo);
const wasVideoMuted = this.isLocalVideoMuted();
const didHaveVideo = !this.isLocalVideoMuted();
const getDesktopStreamPromise = options.desktopStream
? Promise.resolve([ options.desktopStream ])
@@ -1506,8 +1576,7 @@ export default {
// Stores the "untoggle" handler which remembers whether was
// there any video before and whether was it muted.
this._untoggleScreenSharing
= this._turnScreenSharingOff
.bind(this, didHaveVideo, wasVideoMuted);
= this._turnScreenSharingOff.bind(this, didHaveVideo);
desktopStream.on(
JitsiTrackEvents.LOCAL_TRACK_STOPPED,
() => {
@@ -1532,6 +1601,45 @@ export default {
});
},
/**
* Creates a new instance of presenter effect. A new video track is created
* using the new set of constraints that are calculated based on
* the height of the desktop that is being currently shared.
*
* @param {number} height - The height of the desktop stream that is being
* currently shared.
* @param {string} cameraDeviceId - The device id of the camera to be used.
* @return {Promise<JitsiStreamPresenterEffect>} - A promise resolved with
* {@link JitsiStreamPresenterEffect} if it succeeds.
*/
async _createPresenterStreamEffect(height, cameraDeviceId = null) {
let presenterTrack;
try {
presenterTrack = await createLocalPresenterTrack({
cameraDeviceId
},
height);
} catch (err) {
logger.error('Failed to create a camera track for presenter', err);
return;
}
this.localPresenterVideo = presenterTrack;
try {
const effect = await createPresenterEffect(presenterTrack.stream);
APP.store.dispatch(trackAdded(this.localPresenterVideo));
return effect;
} catch (err) {
logger.error('Failed to create the presenter effect', err);
APP.store.dispatch(
setVideoMuted(true, MEDIA_TYPE.PRESENTER));
APP.store.dispatch(notifyCameraError(err));
}
},
/**
* Tries to switch to the screensharing mode by disposing camera stream and
* replacing it with a desktop one.
@@ -1992,36 +2100,56 @@ export default {
const videoWasMuted = this.isLocalVideoMuted();
sendAnalytics(createDeviceChangedEvent('video', 'input'));
createLocalTracksF({
devices: [ 'video' ],
cameraDeviceId,
micDeviceId: null
})
.then(([ stream ]) => {
// if we are in audio only mode or video was muted before
// changing device, then mute
if (this.isAudioOnly() || videoWasMuted) {
return stream.mute()
.then(() => stream);
}
return stream;
})
.then(stream => {
// if we are screen sharing we do not want to stop it
if (this.isSharingScreen) {
return Promise.resolve();
}
// If both screenshare and video are in progress, restart the
// presenter mode with the new camera device.
if (this.isSharingScreen && !videoWasMuted) {
const { height } = this.localVideo.track.getSettings();
return this.useVideoStream(stream);
})
.then(() => {
// dispose the existing presenter track and create a new
// camera track.
APP.store.dispatch(setVideoMuted(true, MEDIA_TYPE.PRESENTER));
return this._createPresenterStreamEffect(height, cameraDeviceId)
.then(effect => this.localVideo.setEffect(effect))
.then(() => {
muteLocalVideo(false);
this.setVideoMuteStatus(false);
logger.log('switched local video device');
this._updateVideoDeviceId();
})
.catch(err => APP.store.dispatch(notifyCameraError(err)));
// If screenshare is in progress but video is muted,
// update the default device id for video.
} else if (this.isSharingScreen && videoWasMuted) {
logger.log('switched local video device');
this._updateVideoDeviceId();
})
.catch(err => {
APP.store.dispatch(notifyCameraError(err));
});
// if there is only video, switch to the new camera stream.
} else {
createLocalTracksF({
devices: [ 'video' ],
cameraDeviceId,
micDeviceId: null
})
.then(([ stream ]) => {
// if we are in audio only mode or video was muted before
// changing device, then mute
if (this.isAudioOnly() || videoWasMuted) {
return stream.mute()
.then(() => stream);
}
return stream;
})
.then(stream => this.useVideoStream(stream))
.then(() => {
logger.log('switched local video device');
this._updateVideoDeviceId();
})
.catch(err => APP.store.dispatch(notifyCameraError(err)));
}
}
);

View File

@@ -205,6 +205,10 @@
border-radius: 6px 0px 6px 6px;
}
.usermessage {
white-space: pre-wrap;
}
&.error {
border-radius: 0px;
@@ -226,6 +230,8 @@
.messagecontent {
margin: 5px 10px;
max-width: 100%;
overflow: hidden;
}
}

View File

@@ -489,6 +489,8 @@
height: 300px;
margin: auto;
position: relative;
top: 50%;
transform: translateY(-50%);
}
#mixedstream {

1
debian/control vendored
View File

@@ -37,6 +37,7 @@ Description: Configuration for web serving of Jitsi Meet
Package: jitsi-meet-prosody
Architecture: all
Depends: openssl, prosody | prosody-trunk | prosody-0.11
Replaces: jitsi-meet-tokens
Description: Prosody configuration for Jitsi Meet
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
Videobridge to provide high quality, scalable video conferences.

View File

@@ -1,2 +1 @@
doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example
doc/debian/jitsi-meet-prosody/README

2
debian/jitsi-meet-prosody.install vendored Normal file
View File

@@ -0,0 +1,2 @@
doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example /usr/share/jitsi-meet-prosody/
resources/prosody-plugins/ /usr/share/jitsi-meet/

View File

@@ -93,7 +93,7 @@ case "$1" in
PROSODY_CONFIG_PRESENT="false"
mkdir -p /etc/prosody/conf.avail/
mkdir -p /etc/prosody/conf.d/
cp /usr/share/doc/jitsi-meet-prosody/prosody.cfg.lua-jvb.example $PROSODY_HOST_CONFIG
cp /usr/share/jitsi-meet-prosody/prosody.cfg.lua-jvb.example $PROSODY_HOST_CONFIG
sed -i "s/jitmeet.example.com/$JVB_HOSTNAME/g" $PROSODY_HOST_CONFIG
sed -i "s/jitmeetSecret/$JVB_SECRET/g" $PROSODY_HOST_CONFIG
sed -i "s/focusSecret/$JICOFO_SECRET/g" $PROSODY_HOST_CONFIG
@@ -179,7 +179,7 @@ case "$1" in
fi
if [ "$PROSODY_CONFIG_PRESENT" = "false" ]; then
invoke-rc.d prosody restart
invoke-rc.d prosody restart || true
fi
;;

View File

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

View File

@@ -78,7 +78,7 @@ case "$1" in
fi
if [ -x "/etc/init.d/prosody" ]; then
invoke-rc.d prosody restart
invoke-rc.d prosody restart || true
fi
fi
else

View File

@@ -41,7 +41,7 @@ case "$1" in
sed -i 's/authentication = "token"/authentication = "anonymous"/g' $PROSODY_HOST_CONFIG
sed -i "s/ app_id=\"$APP_ID\"/ --app_id=\"example_app_id\"/g" $PROSODY_HOST_CONFIG
sed -i "s/ app_secret=\"$APP_SECRET\"/ --app_secret=\"example_app_secret\"/g" $PROSODY_HOST_CONFIG
sed -i 's/ modules_enabled = { "token_verification" }/ --modules_enabled = { "token_verification" }/g' $PROSODY_HOST_CONFIG
sed -i 's/ -- "token_verification"/ "token_verification"/g' $PROSODY_HOST_CONFIG
if [ -x "/etc/init.d/prosody" ]; then
invoke-rc.d prosody restart

View File

@@ -1,4 +1 @@
doc/debian/jitsi-meet/jitsi-meet.example
doc/debian/jitsi-meet/jitsi-meet.example-apache
doc/debian/jitsi-meet/README
config.js

5
debian/jitsi-meet-web-config.install vendored Normal file
View File

@@ -0,0 +1,5 @@
doc/debian/jitsi-meet/jitsi-meet.example /usr/share/jitsi-meet-web-config/
doc/debian/jitsi-meet/jitsi-meet.example-apache /usr/share/jitsi-meet-web-config/
config.js /usr/share/jitsi-meet-web-config/
interface_config.js /etc/jitsi/meet/
logging_config.js /etc/jitsi/meet/

View File

@@ -98,16 +98,17 @@ case "$1" in
# jitsi meet
JITSI_MEET_CONFIG="/etc/jitsi/meet/$JVB_HOSTNAME-config.js"
if [ ! -f $JITSI_MEET_CONFIG ] ; then
cp /usr/share/doc/jitsi-meet-web-config/config.js $JITSI_MEET_CONFIG
cp /usr/share/jitsi-meet-web-config/config.js $JITSI_MEET_CONFIG
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" $JITSI_MEET_CONFIG
fi
JVB_CONFIG="/etc/jitsi/videobridge/sip-communicator.properties"
# this is new install let's configure jvb to serve meet
# no-nginx, no-apache installed on machine, this is new install or reconfiguring old one which have jvb_serve set
if [[ -z "$FORCE_NGINX" && -z "$FORCE_APACHE" && ( -z "$JVB_HOSTNAME_OLD" || ( "$JVB_SERVE" = "true" && "$RECONFIGURING" = "true" )) ]] ; then
JVB_ETC_CONFIG="/etc/jitsi/videobridge/config"
JVB_CONFIG="/etc/jitsi/videobridge/sip-communicator.properties"
# this is a reconfigure, lets just delete old links
if [ "$RECONFIGURING" = "true" ] ; then
@@ -130,8 +131,8 @@ case "$1" in
echo "org.jitsi.videobridge.rest.jetty.ProxyServlet.proxyTo=http://localhost:5280/http-bind" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.resourceBase=/usr/share/jitsi-meet" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./config.js=/etc/jitsi/meet/$JVB_HOSTNAME-config.js" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./interface_config.js=/usr/share/jitsi-meet/interface_config.js" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./logging_config.js=/usr/share/jitsi-meet/logging_config.js" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./interface_config.js=/etc/jitsi/meet/interface_config.js" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./logging_config.js=/etc/jitsi/meet/logging_config.js" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./external_api.js=/usr/share/jitsi-meet/libs/external_api.min.js" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.regex=^/([a-zA-Z0-9]+)$" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.replacement=/" >> $JVB_CONFIG
@@ -167,7 +168,7 @@ case "$1" in
db_set jitsi-meet/jvb-serve "true"
invoke-rc.d jitsi-videobridge restart
invoke-rc.d jitsi-videobridge restart || true
elif [[ "$FORCE_NGINX" = "true" && ( -z "$JVB_HOSTNAME_OLD" || "$RECONFIGURING" = "true" ) ]] ; then
# this is a reconfigure, lets just delete old links
if [ "$RECONFIGURING" = "true" ] ; then
@@ -177,7 +178,7 @@ case "$1" in
# nginx conf
if [ ! -f /etc/nginx/sites-available/$JVB_HOSTNAME.conf ] ; then
cp /usr/share/doc/jitsi-meet-web-config/jitsi-meet.example /etc/nginx/sites-available/$JVB_HOSTNAME.conf
cp /usr/share/jitsi-meet-web-config/jitsi-meet.example /etc/nginx/sites-available/$JVB_HOSTNAME.conf
if [ ! -f /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf ] ; then
ln -s /etc/nginx/sites-available/$JVB_HOSTNAME.conf /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf
fi
@@ -196,7 +197,7 @@ case "$1" in
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
fi
invoke-rc.d nginx reload
invoke-rc.d nginx reload || true
elif [[ "$FORCE_APACHE" = "true" && ( -z "$JVB_HOSTNAME_OLD" || "$RECONFIGURING" = "true" ) ]] ; then
# this is a reconfigure, lets just delete old links
if [ "$RECONFIGURING" = "true" ] ; then
@@ -208,7 +209,7 @@ case "$1" in
if [ ! -f /etc/apache2/sites-available/$JVB_HOSTNAME.conf ] ; then
# when creating new config, make sure all needed modules are enabled
a2enmod rewrite ssl headers proxy_http include
cp /usr/share/doc/jitsi-meet-web-config/jitsi-meet.example-apache /etc/apache2/sites-available/$JVB_HOSTNAME.conf
cp /usr/share/jitsi-meet-web-config/jitsi-meet.example-apache /etc/apache2/sites-available/$JVB_HOSTNAME.conf
a2ensite $JVB_HOSTNAME.conf
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" /etc/apache2/sites-available/$JVB_HOSTNAME.conf
fi
@@ -225,7 +226,33 @@ case "$1" in
/etc/apache2/sites-available/$JVB_HOSTNAME.conf
fi
invoke-rc.d apache2 reload
invoke-rc.d apache2 reload || true
fi
# Moving interface config and logging config to /etc we need to port changes
# to already installed deployments so we do not break them
if [[ -z "$FORCE_NGINX" && -z "$FORCE_APACHE" && "$JVB_SERVE" = "true" ]] ; then
sed -i "s#alias./interface_config.js=/usr/share/jitsi-meet/interface_config.js#alias./interface_config.js=/etc/jitsi/meet/interface_config.js#g" $JVB_CONFIG
sed -i "s#alias./logging_config.js=/usr/share/jitsi-meet/logging_config.js#alias./logging_config.js=/etc/jitsi/meet/logging_config.js#g" $JVB_CONFIG
invoke-rc.d jitsi-videobridge restart || true
elif [[ "$FORCE_NGINX" = "true" ]] ; then
NGINX_CONFIG_FILE="/etc/nginx/sites-available/$JVB_HOSTNAME.conf"
if ! grep -q "location = /interface_config.js" "$NGINX_CONFIG_FILE" ;then
sed -i "s#location = /config.js#location = /interface_config.js { alias /etc/jitsi/meet/interface_config.js; }\n location = /config.js#g" $NGINX_CONFIG_FILE
fi
if ! grep -q "location = /logging_config.js" "$NGINX_CONFIG_FILE" ;then
sed -i "s#location = /config.js#location = /logging_config.js { alias /etc/jitsi/meet/logging_config.js; }\n location = /config.js#g" $NGINX_CONFIG_FILE
fi
invoke-rc.d nginx reload || true
elif [[ "$FORCE_APACHE" = "true" ]] ; then
APACHE_CONFIG_FILE="/etc/apache2/sites-available/$JVB_HOSTNAME.conf"
if ! grep -q "Alias \"/interface_config.js\"" "$APACHE_CONFIG_FILE" ;then
sed -i "s#Alias \"/config.js\"#Alias \"/interface_config.js\" \"/etc/jitsi/meet/interface_config.js\"\n <Location /interface_config.js>\n Require all granted\n </Location>\n Alias \"/config.js\"#g" $APACHE_CONFIG_FILE
fi
if ! grep -q "Alias \"/logging_config.js\"" "$APACHE_CONFIG_FILE" ;then
sed -i "s#Alias \"/config.js\"#Alias \"/logging_config.js\" \"/etc/jitsi/meet/logging_config.js\"\n <Location /logging_config.js>\n Require all granted\n </Location>\n Alias \"/config.js\"#g" $APACHE_CONFIG_FILE
fi
invoke-rc.d apache2 reload || true
fi
echo "----------------"

View File

@@ -1,5 +1,3 @@
interface_config.js /usr/share/jitsi-meet/
logging_config.js /usr/share/jitsi-meet/
*.json /usr/share/jitsi-meet/
*.html /usr/share/jitsi-meet/
*.ico /usr/share/jitsi-meet/

2
debian/rules vendored
View File

@@ -14,7 +14,7 @@ override_dh_auto_build:
override_dh_install: $(LANGUAGES)
dh_installdirs
dh_install -X/config.js -X/package.json
dh_install
$(LANGUAGES):
LOCALE=$$(echo $@ | cut -c1-2) ; \

View File

@@ -1,5 +1,4 @@
-- Plugins path gets uncommented during jitsi-meet-tokens package install - that's where token plugin is located
--plugin_paths = { "/usr/share/jitsi-meet/prosody-plugins/" }
plugin_paths = { "/usr/share/jitsi-meet/prosody-plugins/" }
VirtualHost "jitmeet.example.com"
-- enabled = false -- Remove this line to enable this host
@@ -27,7 +26,10 @@ VirtualHost "jitmeet.example.com"
Component "conference.jitmeet.example.com" "muc"
storage = "null"
--modules_enabled = { "token_verification" }
modules_enabled = {
"muc_meeting_id";
-- "token_verification";
}
admins = { "focusUser@auth.jitmeet.example.com" }
Component "jitsi-videobridge.jitmeet.example.com"

View File

@@ -26,6 +26,12 @@ server {
location = /config.js {
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;
}
location = /interface_config.js {
alias /etc/jitsi/meet/interface_config.js;
}
location = /logging_config.js {
alias /etc/jitsi/meet/logging_config.js;
}
location = /external_api.js {
alias /usr/share/jitsi-meet/libs/external_api.min.js;

View File

@@ -35,6 +35,14 @@
<Location /config.js>
Require all granted
</Location>
Alias "/interface_config.js" "/etc/jitsi/meet/interface_config.js"
<Location /interface_config.js>
Require all granted
</Location>
Alias "/logging_config.js" "/etc/jitsi/meet/logging_config.js"
<Location /logging_config.js>
Require all granted
</Location>
Alias "/external_api.js" "/usr/share/jitsi-meet/libs/external_api.min.js"
<Location /external_api.js>

BIN
images/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="324px" height="63.8px" viewBox="0 0 324 63.8" style="enable-background:new 0 0 324 63.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#0061FF;}
.st1{display:none;}
.st2{display:inline;}
.st3{fill:none;}
</style>
<path class="st0" d="M37.6,12L18.8,24l18.8,12L18.8,48L0,35.9l18.8-12L0,12L18.8,0L37.6,12z M18.7,51.8l18.8-12l18.8,12l-18.8,12
L18.7,51.8z M37.6,35.9l18.8-12L37.6,12L56.3,0l18.8,12L56.3,24l18.8,12L56.3,48L37.6,35.9z"/>
<path d="M89.8,12H105c9.7,0,17.7,5.6,17.7,18.4v2.7c0,12.9-7.5,18.7-17.4,18.7H89.8V12z M98.3,19.2v25.3h6.5c5.5,0,9.2-3.6,9.2-11.6
v-2.1c0-8-3.9-11.6-9.5-11.6H98.3z M127.2,19.6h6.8l1.1,7.5c1.3-5.1,4.6-7.8,10.6-7.8h2.1v8.6h-3.5c-6.9,0-8.6,2.4-8.6,9.2v14.8
h-8.4V19.6H127.2z M149.5,36.4v-0.9c0-10.8,6.9-16.7,16.3-16.7c9.6,0,16.3,5.9,16.3,16.7v0.9c0,10.6-6.5,16.3-16.3,16.3
C155.4,52.6,149.5,47,149.5,36.4z M173.5,36.3v-0.8c0-6-3-9.6-7.8-9.6c-4.7,0-7.8,3.3-7.8,9.6v0.8c0,5.8,3,9.1,7.8,9.1
C170.5,45.3,173.5,42.1,173.5,36.3z M186.5,19.6h7l0.8,6.1c1.7-4.1,5.3-6.9,10.6-6.9c8.2,0,13.6,5.9,13.6,16.8v0.9
c0,10.6-6,16.2-13.6,16.2c-5.1,0-8.6-2.3-10.3-6V63h-8.2L186.5,19.6L186.5,19.6z M210,36.3v-0.7c0-6.4-3.3-9.6-7.7-9.6
c-4.7,0-7.8,3.6-7.8,9.6v0.6c0,5.7,3,9.3,7.7,9.3C207,45.4,210,42.3,210,36.3z M230.9,45.9l-0.7,5.9H223v-43h8.2v16.5
c1.8-4.2,5.4-6.5,10.5-6.5c7.7,0.1,13.4,5.4,13.4,16.1v1c0,10.7-5.4,16.8-13.6,16.8C236.1,52.6,232.6,50.1,230.9,45.9z M246.5,35.9
v-0.8c0-5.9-3.2-9.2-7.7-9.2c-4.6,0-7.8,3.7-7.8,9.3v0.7c0,6,3.1,9.5,7.7,9.5C243.6,45.4,246.5,42.3,246.5,35.9z M258.7,36.4v-0.9
c0-10.8,6.9-16.7,16.3-16.7c9.6,0,16.3,5.9,16.3,16.7v0.9c0,10.6-6.6,16.3-16.3,16.3C264.6,52.6,258.7,47,258.7,36.4z M282.8,36.3
v-0.8c0-6-3-9.6-7.8-9.6c-4.7,0-7.8,3.3-7.8,9.6v0.8c0,5.8,3,9.1,7.8,9.1C279.8,45.3,282.8,42.1,282.8,36.3z M302.3,35.1L291,19.6
h9.7l6.5,9.7l6.6-9.7h9.6L311.9,35L324,51.8h-9.5l-7.4-10.7l-7.2,10.7H290L302.3,35.1z"/>
<g id="Editble" class="st1">
<g class="st2">
<rect x="-105" y="5" class="st3" width="506" height="71.8"/>
<path d="M0.2,13.6h16.3c10.4,0,19,6.1,19,19.8v2.9c0,13.8-8,20-18.7,20H0.2V13.6z M9.4,21.3v27.2h7c5.9,0,9.9-3.9,9.9-12.5v-2.2
c0-8.6-4.1-12.5-10.2-12.5H9.4z M40.4,21.8h7.3l1.1,8c1.4-5.5,4.9-8.3,11.3-8.3h2.2v9.2h-3.7c-7.4,0-9.2,2.6-9.2,9.9v15.8h-9
C40.4,56.4,40.4,21.8,40.4,21.8z M64.3,39.8v-1c0-11.6,7.4-17.9,17.5-17.9c10.3,0,17.5,6.4,17.5,17.9v1c0,11.4-7,17.5-17.5,17.5
C70.6,57.3,64.3,51.2,64.3,39.8z M90.1,39.7v-0.8c0-6.5-3.2-10.3-8.3-10.3c-5,0-8.4,3.5-8.4,10.3v0.8c0,6.2,3.2,9.7,8.3,9.7
C86.9,49.4,90.1,46,90.1,39.7z M104,21.8h7.6l0.9,6.6c1.9-4.4,5.7-7.4,11.4-7.4c8.8,0,14.6,6.4,14.6,18v1
c0,11.4-6.4,17.3-14.6,17.3c-5.5,0-9.2-2.5-11-6.5v17.5H104V21.8z M129.3,39.8V39c0-6.9-3.5-10.3-8.3-10.3c-5,0-8.4,3.8-8.4,10.3
v0.7c0,6.1,3.2,10,8.2,10C126,49.5,129.3,46.1,129.3,39.8z M151.7,50.1l-0.7,6.3h-7.8V10.2h8.8V28c1.9-4.5,5.8-7,11.2-7
c8.2,0.1,14.3,5.8,14.3,17.3v1c0,11.5-5.8,18-14.6,18C157.3,57.3,153.5,54.5,151.7,50.1z M168.5,39.3v-0.8c0-6.4-3.5-9.8-8.3-9.8
c-5,0-8.4,4-8.4,10v0.7c0,6.5,3.3,10.2,8.3,10.2C165.3,49.5,168.5,46.1,168.5,39.3z M181.6,39.8v-1c0-11.6,7.4-17.9,17.5-17.9
c10.3,0,17.5,6.4,17.5,17.9v1c0,11.4-7.1,17.5-17.5,17.5C187.9,57.3,181.6,51.2,181.6,39.8z M207.4,39.7v-0.8
c0-6.5-3.2-10.3-8.3-10.3c-5,0-8.4,3.5-8.4,10.3v0.8c0,6.2,3.2,9.7,8.3,9.7C204.2,49.4,207.4,46,207.4,39.7z M228.3,38.4
l-12.1-16.7h10.4l7,10.4l7.1-10.4H251l-12.3,16.6l13,18h-10.2l-8-11.5l-7.7,11.5h-10.6L228.3,38.4z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

0
images/dropboxLogo_square.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

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

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width='20px' height='20px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-spin"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><g transform="translate(50 50)"><g transform="rotate(0) translate(34 0)"><circle cx="0" cy="0" r="8" fill="#ffffff"><animate attributeName="opacity" from="1" to="0.1" begin="0s" dur="1s" repeatCount="indefinite"></animate><animateTransform attributeName="transform" type="scale" from="1.5" to="1" begin="0s" dur="1s" repeatCount="indefinite"></animateTransform></circle></g><g transform="rotate(45) translate(34 0)"><circle cx="0" cy="0" r="8" fill="#ffffff"><animate attributeName="opacity" from="1" to="0.1" begin="0.12s" dur="1s" repeatCount="indefinite"></animate><animateTransform attributeName="transform" type="scale" from="1.5" to="1" begin="0.12s" dur="1s" repeatCount="indefinite"></animateTransform></circle></g><g transform="rotate(90) translate(34 0)"><circle cx="0" cy="0" r="8" fill="#ffffff"><animate attributeName="opacity" from="1" to="0.1" begin="0.25s" dur="1s" repeatCount="indefinite"></animate><animateTransform attributeName="transform" type="scale" from="1.5" to="1" begin="0.25s" dur="1s" repeatCount="indefinite"></animateTransform></circle></g><g transform="rotate(135) translate(34 0)"><circle cx="0" cy="0" r="8" fill="#ffffff"><animate attributeName="opacity" from="1" to="0.1" begin="0.37s" dur="1s" repeatCount="indefinite"></animate><animateTransform attributeName="transform" type="scale" from="1.5" to="1" begin="0.37s" dur="1s" repeatCount="indefinite"></animateTransform></circle></g><g transform="rotate(180) translate(34 0)"><circle cx="0" cy="0" r="8" fill="#ffffff"><animate attributeName="opacity" from="1" to="0.1" begin="0.5s" dur="1s" repeatCount="indefinite"></animate><animateTransform attributeName="transform" type="scale" from="1.5" to="1" begin="0.5s" dur="1s" repeatCount="indefinite"></animateTransform></circle></g><g transform="rotate(225) translate(34 0)"><circle cx="0" cy="0" r="8" fill="#ffffff"><animate attributeName="opacity" from="1" to="0.1" begin="0.62s" dur="1s" repeatCount="indefinite"></animate><animateTransform attributeName="transform" type="scale" from="1.5" to="1" begin="0.62s" dur="1s" repeatCount="indefinite"></animateTransform></circle></g><g transform="rotate(270) translate(34 0)"><circle cx="0" cy="0" r="8" fill="#ffffff"><animate attributeName="opacity" from="1" to="0.1" begin="0.75s" dur="1s" repeatCount="indefinite"></animate><animateTransform attributeName="transform" type="scale" from="1.5" to="1" begin="0.75s" dur="1s" repeatCount="indefinite"></animateTransform></circle></g><g transform="rotate(315) translate(34 0)"><circle cx="0" cy="0" r="8" fill="#ffffff"><animate attributeName="opacity" from="1" to="0.1" begin="0.87s" dur="1s" repeatCount="indefinite"></animate><animateTransform attributeName="transform" type="scale" from="1.5" to="1" begin="0.87s" dur="1s" repeatCount="indefinite"></animateTransform></circle></g></g></svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -1,64 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="480"
height="270"
id="svg2"
version="1.1"
inkscape:version="0.48.2 r9819"
sodipodi:docname="videomask.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="-16.428571"
inkscape:cy="520"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="998"
inkscape:window-height="711"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-782.36218)">
<rect
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.92795467;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
id="rect2985"
width="479.07202"
height="269.07205"
x="0.46397734"
y="782.82617"
ry="20" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!--#include virtual="base.html" -->
<link rel="apple-touch-icon" href="images/apple-touch-icon.png">
<link rel="stylesheet" href="css/all.css">
<script>

View File

@@ -1,4 +1,4 @@
platform :ios, '10.0'
platform :ios, '11.0'
workspace 'jitsi-meet'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
@@ -82,7 +82,7 @@ post_install do |installer|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'YES'
config.build_settings['SUPPORTS_MACCATALYST'] = 'NO'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0'
end
end
end

View File

@@ -553,6 +553,6 @@ SPEC CHECKSUMS:
RNWatch: 09738b339eceb66e4d80a2371633ca5fb380fa42
Yoga: 02036f6383c0008edb7ef0773a0e6beb6ce82bd1
PODFILE CHECKSUM: 63c90b1d33cd96709fb72bad6be440ae9c3deecb
PODFILE CHECKSUM: 0fdfa45ae809c9460c80be3e0d4bbb822fccc418
COCOAPODS: 1.8.1

View File

@@ -747,7 +747,6 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
INFOPLIST_FILE = src/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = (
@@ -758,7 +757,6 @@
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet;
PRODUCT_NAME = "jitsi-meet";
PROVISIONING_PROFILE_SPECIFIER = "";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@@ -783,7 +781,6 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
INFOPLIST_FILE = src/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = (
@@ -794,7 +791,6 @@
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet;
PRODUCT_NAME = "jitsi-meet";
PROVISIONING_PROFILE_SPECIFIER = "";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
@@ -850,10 +846,11 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@@ -902,10 +899,11 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;

View File

@@ -555,7 +555,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -611,7 +611,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
@@ -639,7 +639,6 @@
ENABLE_BITCODE = NO;
INFOPLIST_FILE = src/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeetSDK.ios;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -650,7 +649,6 @@
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@@ -671,7 +669,6 @@
ENABLE_BITCODE = YES;
INFOPLIST_FILE = src/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeetSDK.ios;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -681,7 +678,6 @@
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};

View File

@@ -47,8 +47,10 @@
},
"chat": {
"error": "Error: your message was not sent. Reason: {{error}}",
"fieldPlaceHolder": "Type your message here",
"messagebox": "Type a message",
"messageTo": "Private message to {{recipient}}",
"noMessagesMessage": "There are no messages in the meeting yet. Start a conversation here!",
"nickname": {
"popover": "Choose a nickname",
"title": "Enter a nickname to use chat"

View File

@@ -454,11 +454,6 @@ export class VideoContainer extends LargeContainer {
const { horizontalIndent, verticalIndent }
= this.getVideoPosition(width, height, containerWidth, containerHeight);
// update avatar position
const top = (containerHeight / 2) - (this.avatarHeight / 4 * 3);
this.$avatar.css('top', top);
this.$wrapper.animate({
width,
height,

42
package-lock.json generated
View File

@@ -10931,8 +10931,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#dd31f0aff0a38b3cfd8e808e457a2e3a0f966514",
"from": "github:jitsi/lib-jitsi-meet#dd31f0aff0a38b3cfd8e808e457a2e3a0f966514",
"version": "github:jitsi/lib-jitsi-meet#4a87f342858963c36bb64a8a0e89d8d7e6e06060",
"from": "github:jitsi/lib-jitsi-meet#4a87f342858963c36bb64a8a0e89d8d7e6e06060",
"requires": {
"@jitsi/sdp-interop": "0.1.14",
"@jitsi/sdp-simulcast": "0.2.2",
@@ -14830,6 +14830,39 @@
"jssha": "^2.2.0"
}
},
"react-native-collapsible": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/react-native-collapsible/-/react-native-collapsible-1.5.1.tgz",
"integrity": "sha512-uQQ2s6l+7+L/pzJroisWsDsyVYVF5bQ+jGbLATNioRh/03SpEL8pcQEVKqVWswcNNR0B9GENixHaLzmuZIwpQg==",
"requires": {
"prop-types": "^15.6.2"
},
"dependencies": {
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.8.1"
}
},
"react-is": {
"version": "16.12.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz",
"integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q=="
}
}
},
"react-native-immersive": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-native-immersive/-/react-native-immersive-2.0.0.tgz",
@@ -14871,6 +14904,11 @@
}
}
},
"react-native-swipe-gestures": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.4.tgz",
"integrity": "sha512-C/vz0KPHNyqHk3uF4Cz/jzd/0N8z34ZgsjAZUh/RsXPH2FtJJf3Fw73pQDWJSoCMtvVadlztb8xQ+/aEQrll7w=="
},
"react-native-swipeout": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/react-native-swipeout/-/react-native-swipeout-2.3.6.tgz",

View File

@@ -57,7 +57,7 @@
"js-utils": "github:jitsi/js-utils#192b1c996e8c05530eb1f19e82a31069c3021e31",
"jsrsasign": "8.0.12",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#dd31f0aff0a38b3cfd8e808e457a2e3a0f966514",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#4a87f342858963c36bb64a8a0e89d8d7e6e06060",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.13",
"moment": "2.19.4",
@@ -72,12 +72,14 @@
"react-native-background-timer": "2.1.1",
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#902e6e92d6bae450a6052f76ba4d02f977ffd8f2",
"react-native-callstats": "3.61.0",
"react-native-collapsible": "1.5.1",
"react-native-immersive": "2.0.0",
"react-native-keep-awake": "4.0.0",
"react-native-linear-gradient": "2.5.6",
"react-native-sound": "0.11.0",
"react-native-svg": "9.7.1",
"react-native-svg-transformer": "0.13.0",
"react-native-swipe-gestures": "1.0.4",
"react-native-swipeout": "2.3.6",
"react-native-watch-connectivity": "0.2.0",
"react-native-webrtc": "1.75.2",

View File

@@ -27,7 +27,7 @@ export default class AmplitudeHandler extends AbstractHandler {
host
};
amplitude.getInstance(this._amplitudeOptions).init(amplitudeAPPKey);
amplitude.getInstance(this._amplitudeOptions).init(amplitudeAPPKey, undefined, { includeReferrer: true });
if (user) {
amplitude.getInstance(this._amplitudeOptions).setUserId(user);

View File

@@ -35,9 +35,12 @@ class Amplitude {
* Sets an identifier for the current user.
*
* @param {string} userId - The new user id.
* @param {string} opt_userId - Currently not used.
* @param {Object} opt_config - Currently not used.
* @param {Function} opt_callback - Currently not used.
* @returns {void}
*/
setUserId(userId) {
setUserId(userId, opt_userId, opt_config, opt_callback) { // eslint-disable-line camelcase, no-unused-vars
AmplitudeNative.setUserId(this._instanceName, userId);
}

View File

@@ -147,9 +147,10 @@ MiddlewareRegistry.register(store => next => action => {
const state = getState();
const { localTracksDuration } = state['features/analytics'];
if (localTracksDuration.conference.startedTime === -1) {
if (localTracksDuration.conference.startedTime === -1 || action.mediaType === 'presenter') {
// We don't want to track the media duration if the conference is not joined yet because otherwise we won't
// be able to compare them with the conference duration (from conference join to conference will leave).
// Also, do not track media duration for presenter tracks.
break;
}
dispatch({

View File

@@ -43,7 +43,9 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
className = { this._getAvatarClassName() }
id = { this.props.id }
style = { this._getAvatarStyle(this.props.color) }>
<Icon src = { url } />
<Icon
size = '50%'
src = { url } />
</div>
);
}

View File

@@ -178,13 +178,15 @@ export function getConferenceName(stateful: Function | Object): string {
* @returns {JitsiConference|undefined}
*/
export function getCurrentConference(stateful: Function | Object) {
const { conference, joining, leaving }
const { conference, joining, leaving, passwordRequired }
= toState(stateful)['features/base/conference'];
return (
conference
? conference === leaving ? undefined : conference
: joining);
// There is a precendence
if (conference) {
return conference === leaving ? undefined : conference;
}
return joining || passwordRequired;
}
/**

View File

@@ -46,6 +46,7 @@ import {
getCurrentConference
} from './functions';
import logger from './logger';
import { MEDIA_TYPE } from '../media';
declare var APP: Object;
@@ -589,7 +590,10 @@ function _syncReceiveVideoQuality({ getState }, next, action) {
function _trackAddedOrRemoved(store, next, action) {
const track = action.track;
if (track && track.local) {
// TODO All track swapping should happen here instead of conference.js.
// Since we swap the tracks for the web client in conference.js, ignore
// presenter tracks here and do not add/remove them to/from the conference.
if (track && track.local && track.mediaType !== MEDIA_TYPE.PRESENTER) {
return (
_syncConferenceLocalTracksWithState(store, action)
.then(() => next(action)));

View File

@@ -2,7 +2,6 @@
import _ from 'lodash';
import Platform from '../react/Platform';
import { equals, ReducerRegistry, set } from '../redux';
import { _UPDATE_CONFIG, CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from './actionTypes';
@@ -21,15 +20,6 @@ import { _cleanupConfig } from './functions';
const INITIAL_NON_RN_STATE = {
};
/**
* When we should enable H.264 on mobile. iOS 10 crashes so we disable it there.
* See: https://bugs.chromium.org/p/webrtc/issues/detail?id=11002
* Note that this is only used for P2P calls.
*
* @type {boolean}
*/
const RN_ENABLE_H264 = navigator.product === 'ReactNative' && !(Platform.OS === 'ios' && Platform.Version === 10);
/**
* The initial state of the feature base/config when executing in a React Native
* environment. The mandatory configuration to be passed to JitsiMeetJS#init().
@@ -50,11 +40,9 @@ const INITIAL_RN_STATE = {
// fastest to merely disable them.
disableAudioLevels: true,
disableH264: !RN_ENABLE_H264,
p2p: {
disableH264: !RN_ENABLE_H264,
preferH264: RN_ENABLE_H264
disableH264: false,
preferH264: true
}
};

View File

@@ -1,7 +1,7 @@
// @flow
import React, { PureComponent, type Node } from 'react';
import { Platform, SafeAreaView, ScrollView, View } from 'react-native';
import { SafeAreaView, ScrollView, View } from 'react-native';
import { ColorSchemeRegistry } from '../../../color-scheme';
import { SlidingView } from '../../../react';
@@ -29,7 +29,12 @@ type Props = {
* Handler for the cancel event, which happens when the user dismisses
* the sheet.
*/
onCancel: ?Function
onCancel: ?Function,
/**
* Function to render a bottom sheet header element, if necessary.
*/
renderHeader: ?Function
};
/**
@@ -43,7 +48,7 @@ class BottomSheet extends PureComponent<Props> {
* @returns {ReactElement}
*/
render() {
const { _styles } = this.props;
const { _styles, renderHeader } = this.props;
return (
<SlidingView
@@ -56,46 +61,23 @@ class BottomSheet extends PureComponent<Props> {
<View
pointerEvents = 'box-none'
style = { styles.sheetAreaCover } />
<View
{ renderHeader && renderHeader() }
<SafeAreaView
style = { [
styles.sheetItemContainer,
_styles.sheet
] }>
{ this._getWrappedContent() }
</View>
<ScrollView
bounces = { false }
showsVerticalScrollIndicator = { false }
style = { styles.scrollView } >
{ this.props.children }
</ScrollView>
</SafeAreaView>
</View>
</SlidingView>
);
}
/**
* Wraps the content when needed (iOS 11 and above), or just returns the original content.
*
* @returns {React$Element}
*/
_getWrappedContent() {
const content = (
<ScrollView
bounces = { false }
showsVerticalScrollIndicator = { false } >
{ this.props.children }
</ScrollView>
);
if (Platform.OS === 'ios') {
const majorVersionIOS = parseInt(Platform.Version, 10);
if (majorVersionIOS > 10) {
return (
<SafeAreaView>
{ content }
</SafeAreaView>
);
}
}
return content;
}
}
/**

View File

@@ -33,6 +33,10 @@ export const bottomSheetStyles = {
flex: 1
},
scrollView: {
paddingHorizontal: MD_ITEM_MARGIN_PADDING
},
/**
* Style for the container of the sheet.
*/
@@ -44,9 +48,7 @@ export const bottomSheetStyles = {
},
sheetItemContainer: {
flex: -1,
maxHeight: '60%',
paddingHorizontal: MD_ITEM_MARGIN_PADDING
flex: -1
}
};
@@ -135,23 +137,45 @@ export const inputDialog = {
* {@link https://material.io/guidelines/components/bottom-sheets.html}.
*/
ColorSchemeRegistry.register('BottomSheet', {
/**
* Style for the {@code Icon} element in a generic item of the menu.
*/
iconStyle: {
color: schemeColor('icon'),
fontSize: 24
buttons: {
/**
* Style for the {@code Icon} element in a generic item of the menu.
*/
iconStyle: {
color: schemeColor('icon'),
fontSize: 24
},
/**
* Style for the label in a generic item rendered in the menu.
*/
labelStyle: {
color: schemeColor('text'),
flexShrink: 1,
fontSize: MD_FONT_SIZE,
marginLeft: 32,
opacity: 0.90
},
/**
* Container style for a generic item rendered in the menu.
*/
style: {
alignItems: 'center',
flexDirection: 'row',
height: MD_ITEM_HEIGHT
},
/**
* Additional style that is not directly used as a style object.
*/
underlayColor: ColorPalette.overflowMenuItemUnderlay
},
/**
* Style for the label in a generic item rendered in the menu.
*/
labelStyle: {
color: schemeColor('text'),
flexShrink: 1,
fontSize: MD_FONT_SIZE,
marginLeft: 32,
opacity: 0.90
expandIcon: {
color: schemeColor('icon'),
fontSize: 16,
opacity: 0.7
},
/**
@@ -159,21 +183,7 @@ ColorSchemeRegistry.register('BottomSheet', {
*/
sheet: {
backgroundColor: schemeColor('background')
},
/**
* Container style for a generic item rendered in the menu.
*/
style: {
alignItems: 'center',
flexDirection: 'row',
height: MD_ITEM_HEIGHT
},
/**
* Additional style that is not directly used as a style object.
*/
underlayColor: ColorPalette.overflowMenuItemUnderlay
}
});
/**

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><defs><path id="a" d="M0 0h24v24H0V0z"/></defs><clipPath id="b"><use xlink:href="#a" overflow="visible"/></clipPath><path clip-path="url(#b)" d="M20 9H4v2h16V9zM4 15h16v-2H4v2z"/></svg>

After

Width:  |  Height:  |  Size: 311 B

View File

@@ -25,6 +25,7 @@ export { default as IconDeviceHeadphone } from './headset.svg';
export { default as IconDeviceSpeaker } from './volume.svg';
export { default as IconDominantSpeaker } from './dominant-speaker.svg';
export { default as IconDownload } from './download.svg';
export { default as IconDragHandle } from './drag-handle.svg';
export { default as IconEventNote } from './event_note.svg';
export { default as IconExitFullScreen } from './exit-full-screen.svg';
export { default as IconFeedback } from './feedback.svg';

View File

@@ -11,7 +11,11 @@ import {
STORE_VIDEO_TRANSFORM,
TOGGLE_CAMERA_FACING_MODE
} from './actionTypes';
import { CAMERA_FACING_MODE, VIDEO_MUTISM_AUTHORITY } from './constants';
import {
CAMERA_FACING_MODE,
MEDIA_TYPE,
VIDEO_MUTISM_AUTHORITY
} from './constants';
/**
* Action to adjust the availability of the local audio.
@@ -89,6 +93,7 @@ export function setVideoAvailable(available: boolean) {
*
* @param {boolean} muted - True if the local video is to be muted or false if
* the local video is to be unmuted.
* @param {MEDIA_TYPE} mediaType - The type of media.
* @param {number} authority - The {@link VIDEO_MUTISM_AUTHORITY} which is
* muting/unmuting the local video.
* @param {boolean} ensureTrack - True if we want to ensure that a new track is
@@ -97,6 +102,7 @@ export function setVideoAvailable(available: boolean) {
*/
export function setVideoMuted(
muted: boolean,
mediaType: MEDIA_TYPE = MEDIA_TYPE.VIDEO,
authority: number = VIDEO_MUTISM_AUTHORITY.USER,
ensureTrack: boolean = false) {
return (dispatch: Dispatch<any>, getState: Function) => {
@@ -107,6 +113,8 @@ export function setVideoMuted(
return dispatch({
type: SET_VIDEO_MUTED,
authority,
mediaType,
ensureTrack,
muted: newValue
});

View File

@@ -15,6 +15,7 @@ export const CAMERA_FACING_MODE = {
*/
export const MEDIA_TYPE = {
AUDIO: 'audio',
PRESENTER: 'presenter',
VIDEO: 'video'
};

View File

@@ -17,7 +17,11 @@ import { getPropertyValue } from '../settings';
import { setTrackMuted, TRACK_ADDED } from '../tracks';
import { setAudioMuted, setCameraFacingMode, setVideoMuted } from './actions';
import { CAMERA_FACING_MODE, VIDEO_MUTISM_AUTHORITY } from './constants';
import {
CAMERA_FACING_MODE,
MEDIA_TYPE,
VIDEO_MUTISM_AUTHORITY
} from './constants';
import logger from './logger';
import {
_AUDIO_INITIAL_MEDIA_STATE,
@@ -45,7 +49,10 @@ MiddlewareRegistry.register(store => next => action => {
const result = next(action);
const { track } = action;
track.local && _syncTrackMutedState(store, track);
// Don't sync track mute state with the redux store for screenshare
// since video mute state represents local camera mute state only.
track.local && track.videoType !== 'desktop'
&& _syncTrackMutedState(store, track);
return result;
}
@@ -72,7 +79,7 @@ function _appStateChanged({ dispatch }, next, action) {
sendAnalytics(createTrackMutedEvent('video', 'background mode', mute));
dispatch(setVideoMuted(mute, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
dispatch(setVideoMuted(mute, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
return next(action);
}
@@ -94,7 +101,11 @@ function _setAudioOnly({ dispatch }, next, action) {
sendAnalytics(createTrackMutedEvent('video', 'audio-only mode', audioOnly));
dispatch(setVideoMuted(audioOnly, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY, ensureVideoTrack));
// Make sure we mute both the desktop and video tracks.
dispatch(setVideoMuted(
audioOnly, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY, ensureVideoTrack));
dispatch(setVideoMuted(
audioOnly, MEDIA_TYPE.PRESENTER, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY, ensureVideoTrack));
return next(action);
}
@@ -231,7 +242,9 @@ function _setRoom({ dispatch, getState }, next, action) {
*/
function _syncTrackMutedState({ getState }, track) {
const state = getState()['features/base/media'];
const muted = Boolean(state[track.mediaType].muted);
const mediaType = track.mediaType === MEDIA_TYPE.PRESENTER
? MEDIA_TYPE.VIDEO : track.mediaType;
const muted = Boolean(state[mediaType].muted);
// XXX If muted state of track when it was added is different from our media
// muted state, we need to mute track and explicitly modify 'muted' property

View File

@@ -1,18 +1,14 @@
// @flow
import React, { Component, type Node } from 'react';
import { Platform, SafeAreaView, StatusBar, View } from 'react-native';
import React, { PureComponent, type Node } from 'react';
import { SafeAreaView, StatusBar, View } from 'react-native';
import { ColorSchemeRegistry } from '../../../color-scheme';
import { connect } from '../../../redux';
import { isDarkColor } from '../../../styles';
import { HEADER_PADDING } from './headerstyles';
/**
* Compatibility header padding size for iOS 10 (and older) devices.
*/
const IOS10_PADDING = 20;
// Register style
import './headerstyles';
/**
* Constanst for the (currently) supported statusbar colors.
@@ -44,19 +40,7 @@ type Props = {
/**
* A generic screen header component.
*/
class Header extends Component<Props> {
/**
* Initializes a new {@code Header} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._getIOS10CompatiblePadding
= this._getIOS10CompatiblePadding.bind(this);
}
class Header extends PureComponent<Props> {
/**
* Implements React's {@link Component#render()}.
*
@@ -66,11 +50,7 @@ class Header extends Component<Props> {
const { _styles } = this.props;
return (
<View
style = { [
_styles.headerOverlay,
this._getIOS10CompatiblePadding()
] } >
<View style = { _styles.headerOverlay }>
<StatusBar
backgroundColor = { _styles.statusBar }
barStyle = { this._getStatusBarContentColor() }
@@ -90,32 +70,6 @@ class Header extends Component<Props> {
);
}
_getIOS10CompatiblePadding: () => Object;
/**
* Adds a padding for iOS 10 (and older) devices to avoid clipping with the
* status bar.
* Note: This is a workaround for iOS 10 (and older) devices only to fix
* usability, but it doesn't take orientation into account, so unnecessary
* padding is rendered in some cases.
*
* @private
* @returns {Object}
*/
_getIOS10CompatiblePadding() {
if (Platform.OS === 'ios') {
const majorVersionIOS = parseInt(Platform.Version, 10);
if (majorVersionIOS <= 10) {
return {
paddingTop: HEADER_PADDING + IOS10_PADDING
};
}
}
return null;
}
/**
* Calculates the color of the statusbar content (light or dark) based on
* certain criterias.

View File

@@ -7,8 +7,7 @@ import { BoxModel } from '../../../styles';
const HEADER_FONT_SIZE = 18;
const HEADER_HEIGHT = 48;
export const HEADER_PADDING = BoxModel.padding / 2;
const HEADER_PADDING = BoxModel.padding / 2;
ColorSchemeRegistry.register('Header', {

View File

@@ -117,8 +117,8 @@ class TestConnectionInfo extends Component<Props, State> {
this.setState({
stats: {
bitrate: {
download: stats.bitrate.download,
upload: stats.bitrate.upload
download: stats.bitrate?.download || 0,
upload: stats.bitrate?.upload || 0
}
}
});

View File

@@ -10,6 +10,47 @@ import {
import logger from './logger';
/**
* Creates a local video track for presenter. The constraints are computed based
* on the height of the desktop that is being shared.
*
* @param {Object} options - The options with which the local presenter track
* is to be created.
* @param {string|null} [options.cameraDeviceId] - Camera device id or
* {@code undefined} to use app's settings.
* @param {number} desktopHeight - The height of the desktop that is being
* shared.
* @returns {Promise<JitsiLocalTrack>}
*/
export async function createLocalPresenterTrack(options, desktopHeight) {
const { cameraDeviceId } = options;
// compute the constraints of the camera track based on the resolution
// of the desktop screen that is being shared.
const cameraHeights = [ 180, 270, 360, 540, 720 ];
const proportion = 4;
const result = cameraHeights.find(
height => (desktopHeight / proportion) < height);
const constraints = {
video: {
aspectRatio: 4 / 3,
height: {
exact: result
}
}
};
const [ videoTrack ] = await JitsiMeetJS.createLocalTracks(
{
cameraDeviceId,
constraints,
devices: [ 'video' ]
});
videoTrack.type = MEDIA_TYPE.PRESENTER;
return videoTrack;
}
/**
* Create local tracks of specific types.
*
@@ -53,11 +94,15 @@ export function createLocalTracksF(
const state = store.getState();
const {
constraints,
desktopSharingFrameRate,
firefox_fake_device, // eslint-disable-line camelcase
resolution
} = state['features/base/config'];
const constraints = options.constraints
?? state['features/base/config'].constraints;
// Do not load blur effect if option for ignoring effects is present.
// This is needed when we are creating a video track for presenter mode.
const loadEffectsPromise = state['features/blur'].blurEnabled
? getBlurEffect()
.then(blurEffect => [ blurEffect ])
@@ -157,6 +202,18 @@ export function getLocalVideoTrack(tracks) {
return getLocalTrack(tracks, MEDIA_TYPE.VIDEO);
}
/**
* Returns the media type of the local video, presenter or video.
*
* @param {Track[]} tracks - List of all tracks.
* @returns {MEDIA_TYPE}
*/
export function getLocalVideoType(tracks) {
const presenterTrack = getLocalTrack(tracks, MEDIA_TYPE.PRESENTER);
return presenterTrack ? MEDIA_TYPE.PRESENTER : MEDIA_TYPE.VIDEO;
}
/**
* Returns track of specified media type for specified participant id.
*
@@ -197,6 +254,29 @@ export function getTracksByMediaType(tracks, mediaType) {
return tracks.filter(t => t.mediaType === mediaType);
}
/**
* Checks if the local video track in the given set of tracks is muted.
*
* @param {Track[]} tracks - List of all tracks.
* @returns {Track[]}
*/
export function isLocalVideoTrackMuted(tracks) {
const presenterTrack = getLocalTrack(tracks, MEDIA_TYPE.PRESENTER);
const videoTrack = getLocalTrack(tracks, MEDIA_TYPE.VIDEO);
// Make sure we check the mute status of only camera tracks, i.e.,
// presenter track when it exists, camera track when the presenter
// track doesn't exist.
if (presenterTrack) {
return isLocalTrackMuted(tracks, MEDIA_TYPE.PRESENTER);
} else if (videoTrack) {
return videoTrack.videoType === 'camera'
? isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO) : true;
}
return true;
}
/**
* Checks if the first local track in the given tracks set is muted.
*

View File

@@ -6,6 +6,7 @@ import {
SET_AUDIO_MUTED,
SET_CAMERA_FACING_MODE,
SET_VIDEO_MUTED,
VIDEO_MUTISM_AUTHORITY,
TOGGLE_CAMERA_FACING_MODE,
toggleCameraFacingMode
} from '../media';
@@ -89,7 +90,7 @@ MiddlewareRegistry.register(store => next => action => {
return;
}
_setMuted(store, action, MEDIA_TYPE.VIDEO);
_setMuted(store, action, action.mediaType);
break;
case TOGGLE_CAMERA_FACING_MODE: {
@@ -131,7 +132,7 @@ MiddlewareRegistry.register(store => next => action => {
const { jitsiTrack } = action.track;
const muted = jitsiTrack.isMuted();
const participantID = jitsiTrack.getParticipantId();
const isVideoTrack = jitsiTrack.isVideoTrack();
const isVideoTrack = jitsiTrack.type !== MEDIA_TYPE.AUDIO;
if (isVideoTrack) {
if (jitsiTrack.isLocal()) {
@@ -255,7 +256,7 @@ function _removeNoDataFromSourceNotification({ getState, dispatch }, track) {
* @private
* @returns {void}
*/
function _setMuted(store, { ensureTrack, muted }, mediaType: MEDIA_TYPE) {
function _setMuted(store, { ensureTrack, authority, muted }, mediaType: MEDIA_TYPE) {
const localTrack
= _getLocalTrack(store, mediaType, /* includePending */ true);
@@ -265,8 +266,12 @@ function _setMuted(store, { ensureTrack, muted }, mediaType: MEDIA_TYPE) {
// `jitsiTrack`, then the `muted` state will be applied once the
// `jitsiTrack` is created.
const { jitsiTrack } = localTrack;
const isAudioOnly = authority === VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY;
jitsiTrack && setTrackMuted(jitsiTrack, muted);
// screenshare cannot be muted or unmuted using the video mute button
// anymore, unless it is muted by audioOnly.
jitsiTrack && (jitsiTrack.videoType !== 'desktop' || isAudioOnly)
&& setTrackMuted(jitsiTrack, muted);
} else if (!muted && ensureTrack && typeof APP === 'undefined') {
// FIXME: This only runs on mobile now because web has its own way of
// creating local tracks. Adjust the check once they are unified.

View File

@@ -15,7 +15,7 @@ export type Props = {
*
* @extends PureComponent
*/
export default class AbstractMessageContainer extends PureComponent<Props> {
export default class AbstractMessageContainer<P: Props> extends PureComponent<P> {
static defaultProps = {
messages: []
};
@@ -46,7 +46,7 @@ export default class AbstractMessageContainer extends PureComponent<Props> {
}
}
groups.push(currentGrouping);
currentGrouping.length && groups.push(currentGrouping);
return groups;
}

View File

@@ -3,6 +3,7 @@
import React, { Component } from 'react';
import { TextInput, TouchableOpacity, View } from 'react-native';
import { translate } from '../../../base/i18n';
import { Icon, IconChatSend } from '../../../base/icons';
import { Platform } from '../../../base/react';
@@ -13,7 +14,12 @@ type Props = {
/**
* Callback to invoke on message send.
*/
onSend: Function
onSend: Function,
/**
* Function to be used to translate i18n labels.
*/
t: Function
};
type State = {
@@ -37,7 +43,7 @@ type State = {
/**
* Implements the chat input bar with text field and action(s).
*/
export default class ChatInputBar extends Component<Props, State> {
class ChatInputBar extends Component<Props, State> {
/**
* Instantiates a new instance of the component.
*
@@ -53,6 +59,7 @@ export default class ChatInputBar extends Component<Props, State> {
};
this._onChangeText = this._onChangeText.bind(this);
this._onFieldReferenceAvailable = this._onFieldReferenceAvailable.bind(this);
this._onFocused = this._onFocused.bind(this);
this._onSubmit = this._onSubmit.bind(this);
}
@@ -76,6 +83,8 @@ export default class ChatInputBar extends Component<Props, State> {
onChangeText = { this._onChangeText }
onFocus = { this._onFocused(true) }
onSubmitEditing = { this._onSubmit }
placeholder = { this.props.t('chat.fieldPlaceHolder') }
ref = { this._onFieldReferenceAvailable }
returnKeyType = 'send'
style = { styles.inputField }
value = { this.state.message } />
@@ -105,6 +114,18 @@ export default class ChatInputBar extends Component<Props, State> {
});
}
_onFieldReferenceAvailable: Object => void;
/**
* Callback to be invoked when the field reference is available.
*
* @param {Object} field - The reference to the field.
* @returns {void}
*/
_onFieldReferenceAvailable(field) {
field && field.focus();
}
_onFocused: boolean => Function;
/**
@@ -138,3 +159,5 @@ export default class ChatInputBar extends Component<Props, State> {
});
}
}
export default translate(ChatInputBar);

View File

@@ -1,18 +1,36 @@
// @flow
import React from 'react';
import { FlatList } from 'react-native';
import { FlatList, Text, View } from 'react-native';
import AbstractMessageContainer, { type Props }
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles';
import AbstractMessageContainer, { type Props as AbstractProps }
from '../AbstractMessageContainer';
import ChatMessageGroup from './ChatMessageGroup';
import styles from './styles';
type Props = AbstractProps & {
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType,
/**
* Function to be used to translate i18n labels.
*/
t: Function
};
/**
* Implements a container to render all the chat messages in a conference.
*/
export default class MessageContainer extends AbstractMessageContainer {
class MessageContainer extends AbstractMessageContainer<Props> {
/**
* Instantiates a new instance of the component.
*
@@ -22,6 +40,7 @@ export default class MessageContainer extends AbstractMessageContainer {
super(props);
this._keyExtractor = this._keyExtractor.bind(this);
this._renderListEmptyComponent = this._renderListEmptyComponent.bind(this);
this._renderMessageGroup = this._renderMessageGroup.bind(this);
}
@@ -31,10 +50,16 @@ export default class MessageContainer extends AbstractMessageContainer {
* @inheritdoc
*/
render() {
const data = this._getMessagesGroupedBySender();
return (
<FlatList
data = { this._getMessagesGroupedBySender() }
inverted = { true }
ListEmptyComponent = { this._renderListEmptyComponent }
data = { data }
// Workaround for RN bug:
// https://github.com/facebook/react-native/issues/21196
inverted = { Boolean(data.length) }
keyExtractor = { this._keyExtractor }
keyboardShouldPersistTaps = 'always'
renderItem = { this._renderMessageGroup }
@@ -58,7 +83,26 @@ export default class MessageContainer extends AbstractMessageContainer {
return `key_${index}`;
}
_renderMessageGroup: Object => React$Element<*>;
_renderListEmptyComponent: () => React$Element<any>;
/**
* Renders a message when there are no messages in the chat yet.
*
* @returns {React$Element<any>}
*/
_renderListEmptyComponent() {
const { _styles, t } = this.props;
return (
<View style = { styles.emptyComponentWrapper }>
<Text style = { _styles.emptyComponentText }>
{ t('chat.noMessagesMessage') }
</Text>
</View>
);
}
_renderMessageGroup: Object => React$Element<any>;
/**
* Renders a single chat message.
@@ -70,3 +114,17 @@ export default class MessageContainer extends AbstractMessageContainer {
return <ChatMessageGroup messages = { messages } />;
}
}
/**
* Maps part of the redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
_styles: ColorSchemeRegistry.get(state, 'Chat')
};
}
export default translate(connect(_mapStateToProps)(MessageContainer));

View File

@@ -42,6 +42,13 @@ export default {
flexDirection: 'column'
},
emptyComponentWrapper: {
alignSelf: 'center',
flex: 1,
padding: BoxModel.padding,
paddingTop: '10%'
},
/**
* A special padding to avoid issues on some devices (such as Android devices with custom suggestions bar).
*/
@@ -143,6 +150,11 @@ ColorSchemeRegistry.register('Chat', {
fontSize: 13
},
emptyComponentText: {
color: schemeColor('displayName'),
textAlign: 'center'
},
localMessageBubble: {
backgroundColor: schemeColor('localMsgBackground'),
borderTopRightRadius: 0

View File

@@ -14,7 +14,7 @@ import ChatMessageGroup from './ChatMessageGroup';
*
* @extends AbstractMessageContainer
*/
export default class MessageContainer extends AbstractMessageContainer {
export default class MessageContainer extends AbstractMessageContainer<Props> {
/**
* Whether or not chat has been scrolled to the bottom of the screen. Used
* to determine if chat should be scrolled automatically to the bottom when

View File

@@ -32,7 +32,7 @@ const EMOTICON_REGEXP_ARRAY: Array<Array<Object>> = [];
// Adding slack-type emoji format
escapedValues.push(escapeRegexp(`:${key}:`));
const regexp = `(${escapedValues.join('|')})`;
const regexp = `\\B(${escapedValues.join('|')})\\B`;
EMOTICON_REGEXP_ARRAY.push([ new RegExp(regexp, 'g'), value ]);
}

View File

@@ -20,7 +20,7 @@ export function notifyKickedOut(participant: Object, _: ?Function) { // eslint-d
return (dispatch: Dispatch<any>, getState: Function) => {
const args = {
participantDisplayName:
getParticipantDisplayName(getState, participant.getDisplayName())
getParticipantDisplayName(getState, participant.getId())
};
dispatch(showNotification({

View File

@@ -3,6 +3,7 @@
import React, { Component } from 'react';
import { getConferenceName } from '../../../base/conference/functions';
import { getParticipantCount } from '../../../base/participants/functions';
import { connect } from '../../../base/redux';
import { isToolboxVisible } from '../../../toolbox';
@@ -13,6 +14,11 @@ import ParticipantsCount from './ParticipantsCount';
*/
type Props = {
/**
* Whether then participant count should be shown or not.
*/
_showParticipantCount: boolean,
/**
* The subject or the of the conference.
* Falls back to conference name.
@@ -39,12 +45,12 @@ class Subject extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const { _subject, _visible } = this.props;
const { _showParticipantCount, _subject, _visible } = this.props;
return (
<div className = { `subject ${_visible ? 'visible' : ''}` }>
<span className = 'subject-text'>{ _subject }</span>
<ParticipantsCount />
{ _showParticipantCount && <ParticipantsCount /> }
</div>
);
}
@@ -62,8 +68,10 @@ class Subject extends Component<Props> {
* }}
*/
function _mapStateToProps(state) {
const participantCount = getParticipantCount(state);
return {
_showParticipantCount: participantCount > 2,
_subject: getConferenceName(state),
_visible: isToolboxVisible(state)
};

View File

@@ -270,8 +270,8 @@ class AudioRoutePickerDialog extends Component<Props, State> {
<View style = { styles.deviceRow } >
<Icon
src = { icon }
style = { [ styles.deviceIcon, _bottomSheetStyles.iconStyle, selectedStyle ] } />
<Text style = { [ styles.deviceText, _bottomSheetStyles.labelStyle, selectedStyle ] } >
style = { [ styles.deviceIcon, _bottomSheetStyles.buttons.iconStyle, selectedStyle ] } />
<Text style = { [ styles.deviceText, _bottomSheetStyles.buttons.labelStyle, selectedStyle ] } >
{ text }
</Text>
</View>
@@ -292,8 +292,8 @@ class AudioRoutePickerDialog extends Component<Props, State> {
<View style = { styles.deviceRow } >
<Icon
src = { deviceInfoMap.SPEAKER.icon }
style = { [ styles.deviceIcon, _bottomSheetStyles.iconStyle ] } />
<Text style = { [ styles.deviceText, _bottomSheetStyles.labelStyle ] } >
style = { [ styles.deviceIcon, _bottomSheetStyles.buttons.iconStyle ] } />
<Text style = { [ styles.deviceText, _bottomSheetStyles.buttons.labelStyle ] } >
{ t('audioDevices.none') }
</Text>
</View>

View File

@@ -1,6 +1,6 @@
// @flow
import { Alert, Platform } from 'react-native';
import { Alert, NativeModules, Platform } from 'react-native';
import uuid from 'uuid';
import { createTrackMutedEvent, sendAnalytics } from '../../analytics';
@@ -36,6 +36,7 @@ import CallKit from './CallKit';
import ConnectionService from './ConnectionService';
import { isCallIntegrationEnabled } from './functions';
const { AudioMode } = NativeModules;
const CallIntegration = CallKit || ConnectionService;
/**
@@ -276,7 +277,8 @@ function _conferenceWillJoin({ dispatch, getState }, next, action) {
}
})
.catch(error => {
// Currently this error code is emitted only by Android.
// Currently this error codes are emitted only by Android.
//
if (error.code === 'CREATE_OUTGOING_CALL_FAILED') {
// We're not tracking the call anymore - it doesn't exist on
// the native side.
@@ -290,6 +292,18 @@ function _conferenceWillJoin({ dispatch, getState }, next, action) {
{ text: 'OK' }
],
{ cancelable: false });
} else if (error.code === 'SECURITY_ERROR') {
// Some devices fail because the CALL_PHONE permission is not granted, which is
// nonsense, because it's not needed for self-managed connections. Alas, this also
// means audio device management would be broken, so fallback to not using ConnectionService.
// NOTE: We are not storing this in Settings, in case it's a transient issue, as far fetched as
// that may be.
if (AudioMode.setUseConnectionService) {
AudioMode.setUseConnectionService(false);
// Set the desired audio mode, since we just reset the whole thing.
AudioMode.setMode(hasVideo ? AudioMode.VIDEO_CALL : AudioMode.AUDIO_CALL);
}
}
});

View File

@@ -80,7 +80,7 @@ class RemoteVideoMenu extends Component<Props> {
afterClick: this._onCancel,
showLabel: true,
participantID: participant.id,
styles: this.props._bottomSheetStyles
styles: this.props._bottomSheetStyles.buttons
};
return (

View File

@@ -0,0 +1,161 @@
// @flow
import {
CLEAR_INTERVAL,
INTERVAL_TIMEOUT,
SET_INTERVAL,
timerWorkerScript
} from './TimeWorker';
/**
* Represents a modified MediaStream that adds video as pip on a desktop stream.
* <tt>JitsiStreamPresenterEffect</tt> does the processing of the original
* desktop stream.
*/
export default class JitsiStreamPresenterEffect {
_canvas: HTMLCanvasElement;
_ctx: CanvasRenderingContext2D;
_desktopElement: HTMLVideoElement;
_desktopStream: MediaStream;
_frameRate: number;
_onVideoFrameTimer: Function;
_onVideoFrameTimerWorker: Function;
_renderVideo: Function;
_videoFrameTimerWorker: Worker;
_videoElement: HTMLVideoElement;
isEnabled: Function;
startEffect: Function;
stopEffect: Function;
/**
* Represents a modified MediaStream that adds a camera track at the
* bottom right corner of the desktop track using a HTML canvas.
* <tt>JitsiStreamPresenterEffect</tt> does the processing of the original
* video stream.
*
* @param {MediaStream} videoStream - The video stream which is user for
* creating the canvas.
*/
constructor(videoStream: MediaStream) {
const videoDiv = document.createElement('div');
const firstVideoTrack = videoStream.getVideoTracks()[0];
const { height, width, frameRate } = firstVideoTrack.getSettings() ?? firstVideoTrack.getConstraints();
this._canvas = document.createElement('canvas');
this._ctx = this._canvas.getContext('2d');
if (document.body !== null) {
document.body.appendChild(this._canvas);
}
this._desktopElement = document.createElement('video');
this._videoElement = document.createElement('video');
videoDiv.appendChild(this._videoElement);
videoDiv.appendChild(this._desktopElement);
if (document.body !== null) {
document.body.appendChild(videoDiv);
}
// Set the video element properties
this._frameRate = parseInt(frameRate, 10);
this._videoElement.width = parseInt(width, 10);
this._videoElement.height = parseInt(height, 10);
this._videoElement.autoplay = true;
this._videoElement.srcObject = videoStream;
// set the style attribute of the div to make it invisible
videoDiv.style.display = 'none';
// Bind event handler so it is only bound once for every instance.
this._onVideoFrameTimer = this._onVideoFrameTimer.bind(this);
this._videoFrameTimerWorker = new Worker(timerWorkerScript);
this._videoFrameTimerWorker.onmessage = this._onVideoFrameTimer;
}
/**
* EventHandler onmessage for the videoFrameTimerWorker WebWorker.
*
* @private
* @param {EventHandler} response - The onmessage EventHandler parameter.
* @returns {void}
*/
_onVideoFrameTimer(response) {
if (response.data.id === INTERVAL_TIMEOUT) {
this._renderVideo();
}
}
/**
* Loop function to render the video frame input and draw presenter effect.
*
* @private
* @returns {void}
*/
_renderVideo() {
// adjust the canvas width/height on every frame incase the window has been resized.
const [ track ] = this._desktopStream.getVideoTracks();
const { height, width } = track.getSettings() ?? track.getConstraints();
this._canvas.width = parseInt(width, 10);
this._canvas.height = parseInt(height, 10);
this._ctx.drawImage(this._desktopElement, 0, 0, this._canvas.width, this._canvas.height);
this._ctx.drawImage(this._videoElement, this._canvas.width - this._videoElement.width, this._canvas.height
- this._videoElement.height, this._videoElement.width, this._videoElement.height);
// draw a border around the video element.
this._ctx.beginPath();
this._ctx.lineWidth = 2;
this._ctx.strokeStyle = '#A9A9A9'; // dark grey
this._ctx.rect(this._canvas.width - this._videoElement.width, this._canvas.height - this._videoElement.height,
this._videoElement.width, this._videoElement.height);
this._ctx.stroke();
}
/**
* Checks if the local track supports this effect.
*
* @param {JitsiLocalTrack} jitsiLocalTrack - Track to apply effect.
* @returns {boolean} - Returns true if this effect can run on the
* specified track, false otherwise.
*/
isEnabled(jitsiLocalTrack: Object) {
return jitsiLocalTrack.isVideoTrack() && jitsiLocalTrack.videoType === 'desktop';
}
/**
* Starts loop to capture video frame and render presenter effect.
*
* @param {MediaStream} desktopStream - Stream to be used for processing.
* @returns {MediaStream} - The stream with the applied effect.
*/
startEffect(desktopStream: MediaStream) {
const firstVideoTrack = desktopStream.getVideoTracks()[0];
const { height, width } = firstVideoTrack.getSettings() ?? firstVideoTrack.getConstraints();
// set the desktop element properties.
this._desktopStream = desktopStream;
this._desktopElement.width = parseInt(width, 10);
this._desktopElement.height = parseInt(height, 10);
this._desktopElement.autoplay = true;
this._desktopElement.srcObject = desktopStream;
this._canvas.width = parseInt(width, 10);
this._canvas.height = parseInt(height, 10);
this._videoFrameTimerWorker.postMessage({
id: SET_INTERVAL,
timeMs: 1000 / this._frameRate
});
return this._canvas.captureStream(this._frameRate);
}
/**
* Stops the capture and render loop.
*
* @returns {void}
*/
stopEffect() {
this._videoFrameTimerWorker.postMessage({
id: CLEAR_INTERVAL
});
}
}

View File

@@ -0,0 +1,62 @@
// @flow
/**
* SET_INTERVAL constant is used to set interval and it is set in
* the id property of the request.data property. timeMs property must
* also be set. request.data example:
*
* {
* id: SET_INTERVAL,
* timeMs: 33
* }
*/
export const SET_INTERVAL = 1;
/**
* CLEAR_INTERVAL constant is used to clear the interval and it is set in
* the id property of the request.data property.
*
* {
* id: CLEAR_INTERVAL
* }
*/
export const CLEAR_INTERVAL = 2;
/**
* INTERVAL_TIMEOUT constant is used as response and it is set in the id
* property.
*
* {
* id: INTERVAL_TIMEOUT
* }
*/
export const INTERVAL_TIMEOUT = 3;
/**
* The following code is needed as string to create a URL from a Blob.
* The URL is then passed to a WebWorker. Reason for this is to enable
* use of setInterval that is not throttled when tab is inactive.
*/
const code = `
var timer;
onmessage = function(request) {
switch (request.data.id) {
case ${SET_INTERVAL}: {
timer = setInterval(() => {
postMessage({ id: ${INTERVAL_TIMEOUT} });
}, request.data.timeMs);
break;
}
case ${CLEAR_INTERVAL}: {
if (timer) {
clearInterval(timer);
}
break;
}
}
};
`;
export const timerWorkerScript
= URL.createObjectURL(new Blob([ code ], { type: 'application/javascript' }));

View File

@@ -0,0 +1,19 @@
// @flow
import JitsiStreamPresenterEffect from './JitsiStreamPresenterEffect';
/**
* Creates a new instance of JitsiStreamPresenterEffect.
*
* @param {MediaStream} stream - The video stream which will be used for
* creating the presenter effect.
* @returns {Promise<JitsiStreamPresenterEffect>}
*/
export function createPresenterEffect(stream: MediaStream) {
if (!MediaStreamTrack.prototype.getSettings
&& !MediaStreamTrack.prototype.getConstraints) {
return Promise.reject(new Error('JitsiStreamPresenterEffect not supported!'));
}
return Promise.resolve(new JitsiStreamPresenterEffect(stream));
}

View File

@@ -10,14 +10,13 @@ import {
import { setAudioOnly } from '../../base/audio-only';
import { translate } from '../../base/i18n';
import {
MEDIA_TYPE,
VIDEO_MUTISM_AUTHORITY,
setVideoMuted
} from '../../base/media';
import { connect } from '../../base/redux';
import { AbstractVideoMuteButton } from '../../base/toolbox';
import type { AbstractButtonProps } from '../../base/toolbox';
import { isLocalTrackMuted } from '../../base/tracks';
import { getLocalVideoType, isLocalVideoTrackMuted } from '../../base/tracks';
import UIEvents from '../../../../service/UI/UIEvents';
declare var APP: Object;
@@ -32,6 +31,11 @@ type Props = AbstractButtonProps & {
*/
_audioOnly: boolean,
/**
* MEDIA_TYPE of the local video.
*/
_videoMediaType: string,
/**
* Whether video is currently muted or not.
*/
@@ -136,10 +140,12 @@ class VideoMuteButton extends AbstractVideoMuteButton<Props, *> {
this.props.dispatch(
setAudioOnly(false, /* ensureTrack */ true));
}
const mediaType = this.props._videoMediaType;
this.props.dispatch(
setVideoMuted(
videoMuted,
mediaType,
VIDEO_MUTISM_AUTHORITY.USER,
/* ensureTrack */ true));
@@ -167,7 +173,8 @@ function _mapStateToProps(state): Object {
return {
_audioOnly: Boolean(audioOnly),
_videoMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO)
_videoMediaType: getLocalVideoType(tracks),
_videoMuted: isLocalVideoTrackMuted(tracks)
};
}

View File

@@ -1,10 +1,13 @@
// @flow
import React, { Component } from 'react';
import { Platform } from 'react-native';
import React, { PureComponent } from 'react';
import { Platform, TouchableOpacity } from 'react-native';
import Collapsible from 'react-native-collapsible';
import GestureRecognizer, { swipeDirections } from 'react-native-swipe-gestures';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { BottomSheet, hideDialog, isDialogOpen } from '../../../base/dialog';
import { IconDragHandle } from '../../../base/icons';
import { CHAT_ENABLED, IOS_RECORDING_ENABLED, getFeatureFlag } from '../../../base/flags';
import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles';
@@ -16,10 +19,12 @@ import { RoomLockButton } from '../../../room-lock';
import { ClosedCaptionButton } from '../../../subtitles';
import { TileViewButton } from '../../../video-layout';
import AudioOnlyButton from './AudioOnlyButton';
import HelpButton from '../HelpButton';
import AudioOnlyButton from './AudioOnlyButton';
import RaiseHandButton from './RaiseHandButton';
import ToggleCameraButton from './ToggleCameraButton';
import styles from './styles';
/**
* The type of the React {@code Component} props of {@link OverflowMenu}.
@@ -52,6 +57,14 @@ type Props = {
dispatch: Function
};
type State = {
/**
* True if the 'more' button set needas to be rendered.
*/
showMore: boolean
}
/**
* The exported React {@code Component}. We need it to execute
* {@link hideDialog}.
@@ -65,7 +78,7 @@ let OverflowMenu_; // eslint-disable-line prefer-const
* Implements a React {@code Component} with some extra actions in addition to
* those in the toolbar.
*/
class OverflowMenu extends Component<Props> {
class OverflowMenu extends PureComponent<Props, State> {
/**
* Initializes a new {@code OverflowMenu} instance.
*
@@ -74,8 +87,15 @@ class OverflowMenu extends Component<Props> {
constructor(props: Props) {
super(props);
this.state = {
showMore: false
};
// Bind event handlers so they are only bound once per instance.
this._onCancel = this._onCancel.bind(this);
this._onSwipe = this._onSwipe.bind(this);
this._onToggleMenu = this._onToggleMenu.bind(this);
this._renderMenuExpandToggle = this._renderMenuExpandToggle.bind(this);
}
/**
@@ -85,37 +105,71 @@ class OverflowMenu extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const { _bottomSheetStyles } = this.props;
const { showMore } = this.state;
const buttonProps = {
afterClick: this._onCancel,
showLabel: true,
styles: this.props._bottomSheetStyles
styles: _bottomSheetStyles.buttons
};
return (
<BottomSheet onCancel = { this._onCancel }>
<BottomSheet
onCancel = { this._onCancel }
renderHeader = { this._renderMenuExpandToggle }>
<AudioRouteButton { ...buttonProps } />
<ToggleCameraButton { ...buttonProps } />
<AudioOnlyButton { ...buttonProps } />
<RoomLockButton { ...buttonProps } />
<ClosedCaptionButton { ...buttonProps } />
{
this.props._recordingEnabled
&& <RecordButton { ...buttonProps } />
}
<LiveStreamButton { ...buttonProps } />
<TileViewButton { ...buttonProps } />
<InviteButton { ...buttonProps } />
{
this.props._chatEnabled
&& <InfoDialogButton { ...buttonProps } />
}
<RaiseHandButton { ...buttonProps } />
<SharedDocumentButton { ...buttonProps } />
<HelpButton { ...buttonProps } />
<Collapsible collapsed = { !showMore }>
<RoomLockButton { ...buttonProps } />
<ClosedCaptionButton { ...buttonProps } />
{
this.props._recordingEnabled
&& <RecordButton { ...buttonProps } />
}
<LiveStreamButton { ...buttonProps } />
<TileViewButton { ...buttonProps } />
<InviteButton { ...buttonProps } />
{
this.props._chatEnabled
&& <InfoDialogButton { ...buttonProps } />
}
<RaiseHandButton { ...buttonProps } />
<SharedDocumentButton { ...buttonProps } />
<HelpButton { ...buttonProps } />
</Collapsible>
</BottomSheet>
);
}
_renderMenuExpandToggle: () => React$Element<any>;
/**
* Function to render the menu toggle in the bottom sheet header area.
*
* @returns {React$Element}
*/
_renderMenuExpandToggle() {
return (
<GestureRecognizer
config = {{
velocityThreshold: 0.1,
directionalOffsetThreshold: 30
}}
onSwipe = { this._onSwipe }
style = { [
this.props._bottomSheetStyles.sheet,
styles.expandMenuContainer
] }>
<TouchableOpacity onPress = { this._onToggleMenu }>
{ /* $FlowFixMeProps */ }
<IconDragHandle style = { this.props._bottomSheetStyles.expandIcon } />
</TouchableOpacity>
</GestureRecognizer>
);
}
_onCancel: () => boolean;
/**
@@ -133,6 +187,50 @@ class OverflowMenu extends Component<Props> {
return false;
}
_onSwipe: (string) => void;
/**
* Callback to be invoked when a swipe gesture is detected on the menu.
*
* @param {string} gestureName - The name of the swipe gesture.
* @returns {void}
*/
_onSwipe(gestureName) {
const { showMore } = this.state;
switch (gestureName) {
case swipeDirections.SWIPE_UP:
!showMore && this.setState({
showMore: true
});
break;
case swipeDirections.SWIPE_DOWN:
if (showMore) {
// If the menu is expanded, we collapse it.
this.setState({
showMore: false
});
} else {
// If the menu is not expanded, we close the menu
this._onCancel();
}
break;
}
}
_onToggleMenu: () => void;
/**
* Callback to be invoked when the expand menu button is pressed.
*
* @returns {void}
*/
_onToggleMenu() {
this.setState({
showMore: !this.state.showMore
});
}
}
/**

View File

@@ -55,6 +55,11 @@ const whiteToolbarButtonIcon = {
*/
const styles = {
expandMenuContainer: {
alignItems: 'center',
flexDirection: 'column'
},
/**
* The style of the toolbar.
*/

View File

@@ -57,7 +57,10 @@ import {
} from '../../../settings';
import { toggleSharedVideo } from '../../../shared-video';
import { SpeakerStats } from '../../../speaker-stats';
import { TileViewButton } from '../../../video-layout';
import {
TileViewButton,
toggleTileView
} from '../../../video-layout';
import {
OverflowMenuVideoQualityItem,
VideoQualityDialog
@@ -122,6 +125,11 @@ type Props = {
*/
_fullScreen: boolean,
/**
* Whether or not the tile view is enabled.
*/
_tileViewEnabled: boolean,
/**
* Whether or not invite should be hidden, regardless of feature
* availability.
@@ -236,6 +244,7 @@ class Toolbox extends Component<Props, State> {
this._onToolbarToggleScreenshare = this._onToolbarToggleScreenshare.bind(this);
this._onToolbarToggleSharedVideo = this._onToolbarToggleSharedVideo.bind(this);
this._onToolbarOpenLocalRecordingInfoDialog = this._onToolbarOpenLocalRecordingInfoDialog.bind(this);
this._onShortcutToggleTileView = this._onShortcutToggleTileView.bind(this);
this.state = {
windowWidth: window.innerWidth
@@ -274,6 +283,11 @@ class Toolbox extends Component<Props, State> {
character: 'S',
exec: this._onShortcutToggleFullScreen,
helpDescription: 'keyboardShortcuts.fullScreen'
},
this._shouldShowButton('tileview') && {
character: 'W',
exec: this._onShortcutToggleTileView,
helpDescription: 'toolbar.tileViewToggle'
}
];
@@ -475,6 +489,16 @@ class Toolbox extends Component<Props, State> {
this.props.dispatch(toggleDialog(VideoQualityDialog));
}
/**
* Dispaches an action to toggle tile view.
*
* @private
* @returns {void}
*/
_doToggleTileView() {
this.props.dispatch(toggleTileView());
}
_onMouseOut: () => void;
/**
@@ -565,6 +589,24 @@ class Toolbox extends Component<Props, State> {
this._doToggleVideoQuality();
}
_onShortcutToggleTileView: () => void;
/**
* Dispatches an action for toggling the tile view.
*
* @private
* @returns {void}
*/
_onShortcutToggleTileView() {
sendAnalytics(createShortcutEvent(
'toggle.tileview',
{
enable: !this.props._tileViewEnabled
}));
this._doToggleTileView();
}
_onShortcutToggleFullScreen: () => void;
/**
@@ -1295,6 +1337,7 @@ function _mapStateToProps(state) {
iAmRecorder || (!addPeopleEnabled && !dialOutEnabled),
_isGuest: state['features/base/jwt'].isGuest,
_fullScreen: fullScreen,
_tileViewEnabled: state['features/video-layout'].tileViewEnabled,
_localParticipantID: localParticipant.id,
_localRecState: localRecordingStates,
_overflowMenuVisible: overflowMenuVisible,

View File

@@ -101,6 +101,7 @@ export class AbstractWelcomePage extends Component<Props, *> {
*/
componentDidMount() {
this._mounted = true;
sendAnalytics(createWelcomePageEvent('viewed', undefined, { value: 1 }));
}
/**

View File

@@ -116,6 +116,8 @@ class WelcomePage extends AbstractWelcomePage {
* @returns {void}
*/
componentDidMount() {
super.componentDidMount();
document.body.classList.add('welcome-page');
document.title = interfaceConfig.APP_NAME;

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,34 @@
local uuid_gen = require "util.uuid".generate;
-- Module that generates a unique meetingId, attaches it to the room
-- and adds it to all disco info form data (when room is queried or in the
-- initial room owner config)
-- Hook to assign meetingId for new rooms
module:hook("muc-room-created", function(event)
local room = event.room;
room._data.meetingId = uuid_gen();
module:log("debug", "Created meetingId:%s for %s",
room._data.meetingId, room.jid);
end);
-- Returns the meeting config Id form data.
function getMeetingIdConfig(room)
return {
name = "muc#roominfo_meetingId";
type = "text-single";
label = "The meeting unique id.";
value = room._data.meetingId or "";
};
end
-- add meeting Id to the disco info requests to the room
module:hook("muc-disco#info", function(event)
table.insert(event.form, getMeetingIdConfig(event.room));
end);
-- add the meeting Id in the default config we return to jicofo
module:hook("muc-config-form", function(event)
table.insert(event.form, getMeetingIdConfig(event.room));
end, 90-3);