Compare commits
32 Commits
3728
...
conf-updat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68384dcb3c | ||
|
|
f29632a224 | ||
|
|
5d30e007b4 | ||
|
|
965106e66f | ||
|
|
1aaaae24ee | ||
|
|
9191000da4 | ||
|
|
8eb93086bd | ||
|
|
b64294af6d | ||
|
|
bbf33a8895 | ||
|
|
bcc1289a23 | ||
|
|
58bd48c1ae | ||
|
|
1a3736bf98 | ||
|
|
0eec182df4 | ||
|
|
c526844eb2 | ||
|
|
d856c1f328 | ||
|
|
15e47a9eb3 | ||
|
|
da98d39b61 | ||
|
|
411bafb5a6 | ||
|
|
0a64bf2068 | ||
|
|
db6a2673de | ||
|
|
e11d4d3101 | ||
|
|
8fd3bb2302 | ||
|
|
fb3a832a52 | ||
|
|
9c146c1245 | ||
|
|
792f506425 | ||
|
|
6121e9fc65 | ||
|
|
955fa1f49f | ||
|
|
2544d0a084 | ||
|
|
8f0a12016a | ||
|
|
34ccd3524f | ||
|
|
563e99ecd3 | ||
|
|
70f14be50f |
11
README.md
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
216
conference.js
@@ -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)));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -489,6 +489,8 @@
|
||||
height: 300px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
#mixedstream {
|
||||
|
||||
1
debian/control
vendored
@@ -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.
|
||||
|
||||
1
debian/jitsi-meet-prosody.docs
vendored
@@ -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
@@ -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/
|
||||
4
debian/jitsi-meet-prosody.postinst
vendored
@@ -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
|
||||
;;
|
||||
|
||||
|
||||
1
debian/jitsi-meet-tokens.install
vendored
@@ -1 +0,0 @@
|
||||
resources/prosody-plugins/ /usr/share/jitsi-meet/
|
||||
2
debian/jitsi-meet-tokens.postinst
vendored
@@ -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
|
||||
|
||||
2
debian/jitsi-meet-tokens.postrm
vendored
@@ -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
|
||||
|
||||
3
debian/jitsi-meet-web-config.docs
vendored
@@ -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
@@ -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/
|
||||
45
debian/jitsi-meet-web-config.postinst
vendored
@@ -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 "----------------"
|
||||
|
||||
2
debian/jitsi-meet-web.install
vendored
@@ -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
@@ -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) ; \
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
@@ -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
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 15 KiB |
BIN
images/ie.png
|
Before Width: | Height: | Size: 4.1 KiB |
@@ -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 |
BIN
images/opera.png
|
Before Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 13 KiB |
@@ -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 |
@@ -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 |
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -553,6 +553,6 @@ SPEC CHECKSUMS:
|
||||
RNWatch: 09738b339eceb66e4d80a2371633ca5fb380fa42
|
||||
Yoga: 02036f6383c0008edb7ef0773a0e6beb6ce82bd1
|
||||
|
||||
PODFILE CHECKSUM: 63c90b1d33cd96709fb72bad6be440ae9c3deecb
|
||||
PODFILE CHECKSUM: 0fdfa45ae809c9460c80be3e0d4bbb822fccc418
|
||||
|
||||
COCOAPODS: 1.8.1
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
1
react/features/base/icons/svg/drag-handle.svg
Normal 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 |
@@ -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';
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ export const CAMERA_FACING_MODE = {
|
||||
*/
|
||||
export const MEDIA_TYPE = {
|
||||
AUDIO: 'audio',
|
||||
PRESENTER: 'presenter',
|
||||
VIDEO: 'video'
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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', {
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ]);
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
62
react/features/stream-effects/presenter/TimeWorker.js
Normal 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' }));
|
||||
19
react/features/stream-effects/presenter/index.js
Normal 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));
|
||||
}
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,6 +55,11 @@ const whiteToolbarButtonIcon = {
|
||||
*/
|
||||
const styles = {
|
||||
|
||||
expandMenuContainer: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the toolbar.
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -101,6 +101,7 @@ export class AbstractWelcomePage extends Component<Props, *> {
|
||||
*/
|
||||
componentDidMount() {
|
||||
this._mounted = true;
|
||||
sendAnalytics(createWelcomePageEvent('viewed', undefined, { value: 1 }));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -116,6 +116,8 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
|
||||
document.body.classList.add('welcome-page');
|
||||
document.title = interfaceConfig.APP_NAME;
|
||||
|
||||
|
||||
BIN
resources/img/appstore-badge.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
resources/img/f-droid-badge.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
resources/img/google-play-badge.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
34
resources/prosody-plugins/mod_muc_meeting_id.lua
Normal 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);
|
||||