Compare commits
70 Commits
4471
...
prosody-mo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b022ce60ac | ||
|
|
20ce38bd4c | ||
|
|
c4ba97e87c | ||
|
|
4b8aae90e0 | ||
|
|
c2539bf615 | ||
|
|
4fdd4b66f7 | ||
|
|
9fa29d7353 | ||
|
|
c14f639639 | ||
|
|
c007477ee9 | ||
|
|
50997ae6ac | ||
|
|
f8a41aea9c | ||
|
|
88c02fb658 | ||
|
|
0f64c66f91 | ||
|
|
9f65ae52f1 | ||
|
|
a242e86b23 | ||
|
|
4211db0893 | ||
|
|
9a35026d6a | ||
|
|
9742e90bb5 | ||
|
|
2a01d3550c | ||
|
|
efce5a831b | ||
|
|
e0117e03e8 | ||
|
|
1648e4b407 | ||
|
|
b02136d013 | ||
|
|
734631a7a4 | ||
|
|
9fbb35b6e1 | ||
|
|
f45af351d8 | ||
|
|
1f4cd22875 | ||
|
|
53cc724b3b | ||
|
|
b9ccc3ad8c | ||
|
|
68a0bdce2c | ||
|
|
b71d92a139 | ||
|
|
30fc04ba61 | ||
|
|
d2046c2c8f | ||
|
|
35b5f6df06 | ||
|
|
ca2343c31a | ||
|
|
3657c19e60 | ||
|
|
007183c151 | ||
|
|
9c10ac3028 | ||
|
|
4b429112f2 | ||
|
|
d067c4e731 | ||
|
|
07d8611988 | ||
|
|
b0d55f9450 | ||
|
|
5f2ee6d951 | ||
|
|
ddea7d0294 | ||
|
|
348c6416e5 | ||
|
|
ad265d5815 | ||
|
|
d5b2da02c1 | ||
|
|
fbfaed07b2 | ||
|
|
da33d8a033 | ||
|
|
830817d7b4 | ||
|
|
8c67f1fdf3 | ||
|
|
b57da04553 | ||
|
|
b428c3bca8 | ||
|
|
96c34b7774 | ||
|
|
f2bbc874b3 | ||
|
|
b18398f016 | ||
|
|
a6e58c3101 | ||
|
|
c5f6df5210 | ||
|
|
e67c08d837 | ||
|
|
d854b2cd3d | ||
|
|
ab5c8d49c3 | ||
|
|
820d9b2ba8 | ||
|
|
e4c1046d7c | ||
|
|
223187c640 | ||
|
|
35e8821679 | ||
|
|
3125345793 | ||
|
|
5e6c4d67ed | ||
|
|
a3fb996ff0 | ||
|
|
65a9de346f | ||
|
|
036d810d46 |
4
.github/ISSUE_TEMPLATE/2-feature-request.md
vendored
@@ -1,7 +1,9 @@
|
||||
---
|
||||
name: "Feature request"
|
||||
about: Suggest an idea for this project
|
||||
|
||||
title: ''
|
||||
labels: 'feature-request'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
@@ -2,10 +2,3 @@
|
||||
* Notifies interested parties that hangup procedure will start.
|
||||
*/
|
||||
export const BEFORE_HANGUP = 'conference.before_hangup';
|
||||
|
||||
/**
|
||||
* Notifies interested parties that desktop sharing enable/disable state is
|
||||
* changed.
|
||||
*/
|
||||
export const DESKTOP_SHARING_ENABLED_CHANGED
|
||||
= 'conference.desktop_sharing_enabled_changed';
|
||||
|
||||
@@ -18,6 +18,10 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
# This one fixes a weird WebRTC runtime problem on some devices.
|
||||
# https://github.com/jitsi/jitsi-meet/issues/7911#issuecomment-714323255
|
||||
android.enableDexingArtifactTransform.desugaring=false
|
||||
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<uses-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
@@ -34,8 +34,7 @@
|
||||
android:launchMode="singleTask"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
android:windowSoftInputMode="adjustResize"></activity>
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
|
||||
<service
|
||||
@@ -46,7 +45,9 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService" />
|
||||
<service
|
||||
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
|
||||
android:foregroundServiceType="mediaProjection" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -43,6 +43,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = NAME;
|
||||
|
||||
private static boolean isSupported;
|
||||
private boolean isDisabled;
|
||||
|
||||
public PictureInPictureModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
@@ -83,6 +84,10 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
public void enterPictureInPicture() {
|
||||
if (isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSupported) {
|
||||
throw new IllegalStateException("Picture-in-Picture not supported");
|
||||
}
|
||||
@@ -126,6 +131,11 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setPictureInPictureDisabled(Boolean disabled) {
|
||||
this.isDisabled = disabled;
|
||||
}
|
||||
|
||||
public boolean isPictureInPictureSupported() {
|
||||
return isSupported;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
reloadWithStoredParams
|
||||
} from './react/features/app/actions';
|
||||
import {
|
||||
AVATAR_ID_COMMAND,
|
||||
AVATAR_URL_COMMAND,
|
||||
EMAIL_COMMAND,
|
||||
authStatusChanged,
|
||||
@@ -42,8 +41,7 @@ import {
|
||||
lockStateChanged,
|
||||
onStartMutedPolicyChanged,
|
||||
p2pStatusChanged,
|
||||
sendLocalParticipant,
|
||||
setDesktopSharingEnabled
|
||||
sendLocalParticipant
|
||||
} from './react/features/base/conference';
|
||||
import {
|
||||
checkAndNotifyForNewDevice,
|
||||
@@ -66,6 +64,8 @@ import {
|
||||
JitsiTrackEvents
|
||||
} from './react/features/base/lib-jitsi-meet';
|
||||
import {
|
||||
getStartWithAudioMuted,
|
||||
getStartWithVideoMuted,
|
||||
isVideoMutedByUser,
|
||||
MEDIA_TYPE,
|
||||
setAudioAvailable,
|
||||
@@ -97,7 +97,7 @@ import {
|
||||
destroyLocalTracks,
|
||||
getLocalJitsiAudioTrack,
|
||||
getLocalJitsiVideoTrack,
|
||||
isLocalVideoTrackMuted,
|
||||
isLocalCameraTrackMuted,
|
||||
isLocalTrackMuted,
|
||||
isUserInteractionRequiredForUnmute,
|
||||
replaceLocalTrack,
|
||||
@@ -169,7 +169,6 @@ window.JitsiMeetScreenObtainer = {
|
||||
* Known custom conference commands.
|
||||
*/
|
||||
const commands = {
|
||||
AVATAR_ID: AVATAR_ID_COMMAND,
|
||||
AVATAR_URL: AVATAR_URL_COMMAND,
|
||||
CUSTOM_ROLE: 'custom-role',
|
||||
EMAIL: EMAIL_COMMAND,
|
||||
@@ -441,17 +440,8 @@ export default {
|
||||
* the tracks won't exist).
|
||||
*/
|
||||
_localTracksInitialized: false,
|
||||
isSharingScreen: false,
|
||||
|
||||
/**
|
||||
* Indicates if the desktop sharing functionality has been enabled.
|
||||
* It takes into consideration the status returned by
|
||||
* {@link JitsiMeetJS.isDesktopSharingEnabled()}. The latter can be false
|
||||
* either if the desktop sharing is not supported by the current browser
|
||||
* or if it was disabled through lib-jitsi-meet specific options (check
|
||||
* config.js for listed options).
|
||||
*/
|
||||
isDesktopSharingEnabled: false,
|
||||
isSharingScreen: false,
|
||||
|
||||
/**
|
||||
* The local audio track (if any).
|
||||
@@ -679,14 +669,6 @@ export default {
|
||||
con.addEventListener(JitsiConnectionEvents.CONNECTION_FAILED, _connectionFailedHandler);
|
||||
APP.connection = connection = con;
|
||||
|
||||
// Desktop sharing related stuff:
|
||||
this.isDesktopSharingEnabled
|
||||
= JitsiMeetJS.isDesktopSharingEnabled();
|
||||
eventEmitter.emit(JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED, this.isDesktopSharingEnabled);
|
||||
|
||||
APP.store.dispatch(
|
||||
setDesktopSharingEnabled(this.isDesktopSharingEnabled));
|
||||
|
||||
this._createRoom(tracks);
|
||||
APP.remoteControl.init();
|
||||
|
||||
@@ -733,10 +715,10 @@ export default {
|
||||
const initialOptions = {
|
||||
startAudioOnly: config.startAudioOnly,
|
||||
startScreenSharing: config.startScreenSharing,
|
||||
startWithAudioMuted: config.startWithAudioMuted
|
||||
startWithAudioMuted: getStartWithAudioMuted(APP.store.getState())
|
||||
|| config.startSilent
|
||||
|| isUserInteractionRequiredForUnmute(APP.store.getState()),
|
||||
startWithVideoMuted: config.startWithVideoMuted
|
||||
startWithVideoMuted: getStartWithVideoMuted(APP.store.getState())
|
||||
|| isUserInteractionRequiredForUnmute(APP.store.getState())
|
||||
};
|
||||
|
||||
@@ -811,7 +793,7 @@ export default {
|
||||
isLocalVideoMuted() {
|
||||
// If the tracks are not ready, read from base/media state
|
||||
return this._localTracksInitialized
|
||||
? isLocalVideoTrackMuted(
|
||||
? isLocalCameraTrackMuted(
|
||||
APP.store.getState()['features/base/tracks'])
|
||||
: isVideoMutedByUser(APP.store);
|
||||
},
|
||||
@@ -1134,20 +1116,6 @@ export default {
|
||||
return room ? room.getParticipantById(id) : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get participant connection status for the participant.
|
||||
*
|
||||
* @param {string} id participant's identifier(MUC nickname)
|
||||
*
|
||||
* @returns {ParticipantConnectionStatus|null} the status of the participant
|
||||
* or null if no such participant is found or participant is the local user.
|
||||
*/
|
||||
getParticipantConnectionStatus(id) {
|
||||
const participant = this.getParticipantById(id);
|
||||
|
||||
return participant ? participant.getConnectionStatus() : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the display name foe the <tt>JitsiParticipant</tt> identified by
|
||||
* the given <tt>id</tt>.
|
||||
@@ -1546,9 +1514,8 @@ export default {
|
||||
if (this.videoSwitchInProgress) {
|
||||
return Promise.reject('Switch in progress.');
|
||||
}
|
||||
if (!this.isDesktopSharingEnabled) {
|
||||
return Promise.reject(
|
||||
'Cannot toggle screen sharing: not supported.');
|
||||
if (!JitsiMeetJS.isDesktopSharingEnabled()) {
|
||||
return Promise.reject('Cannot toggle screen sharing: not supported.');
|
||||
}
|
||||
|
||||
if (this.isAudioOnly()) {
|
||||
@@ -2136,16 +2103,6 @@ export default {
|
||||
}));
|
||||
});
|
||||
|
||||
room.addCommandListener(this.commands.defaults.AVATAR_ID,
|
||||
(data, from) => {
|
||||
APP.store.dispatch(
|
||||
participantUpdated({
|
||||
conference: room,
|
||||
id: from,
|
||||
avatarID: data.value
|
||||
}));
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.NICKNAME_CHANGED,
|
||||
this.changeLocalDisplayName.bind(this));
|
||||
|
||||
@@ -2614,6 +2571,20 @@ export default {
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=997689
|
||||
const hasDefaultMicChanged = newDevices.audioinput === 'default';
|
||||
|
||||
// This is the case when the local video is muted and a preferred device is connected.
|
||||
if (requestedInput.video && this.isLocalVideoMuted()) {
|
||||
// We want to avoid creating a new video track in order to prevent turning on the camera.
|
||||
requestedInput.video = false;
|
||||
APP.store.dispatch(updateSettings({ // Update the current selected camera for the device selection dialog.
|
||||
cameraDeviceId: newDevices.videoinput
|
||||
}));
|
||||
delete newDevices.videoinput;
|
||||
|
||||
// Removing the current video track in order to force the unmute to select the preferred device.
|
||||
this.useVideoStream(null);
|
||||
|
||||
}
|
||||
|
||||
promises.push(
|
||||
mediaDeviceHelper.createLocalTracksAfterDeviceListChanged(
|
||||
createLocalTracksF,
|
||||
@@ -3035,7 +3006,7 @@ export default {
|
||||
* @param {boolean} muted - New muted status.
|
||||
*/
|
||||
setVideoMuteStatus(muted) {
|
||||
APP.UI.setVideoMuted(this.getMyUserId(), muted);
|
||||
APP.UI.setVideoMuted(this.getMyUserId());
|
||||
APP.API.notifyVideoMutedStatusChanged(muted);
|
||||
},
|
||||
|
||||
|
||||
@@ -359,17 +359,12 @@ var config = {
|
||||
// Default language for the user interface.
|
||||
// defaultLanguage: 'en',
|
||||
|
||||
// If true all users without a token will be considered guests and all users
|
||||
// with token will be considered non-guests. Only guests will be allowed to
|
||||
// edit their profile.
|
||||
enableUserRolesBasedOnToken: false,
|
||||
// Disables profile and the edit of all fields from the profile settings (display name and email)
|
||||
// disableProfile: false,
|
||||
|
||||
// Whether or not some features are checked based on token.
|
||||
// enableFeaturesBasedOnToken: false,
|
||||
|
||||
// Enable lock room for all moderators, even when userRolesBasedOnToken is enabled and participants are guests.
|
||||
// lockRoomGuestEnabled: false,
|
||||
|
||||
// When enabled the password used for locking a room is restricted to up to the number of digits specified
|
||||
// roomPasswordNumberOfDigits: 10,
|
||||
// default: roomPasswordNumberOfDigits: false,
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
|
||||
.meetings-list-empty {
|
||||
text-align: center;
|
||||
@@ -20,11 +19,34 @@
|
||||
flex-direction: column;
|
||||
|
||||
.description {
|
||||
font-size: 16px;
|
||||
padding: 20px;
|
||||
color: #2f3237;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin-bottom: 16px;
|
||||
max-width: 436px;
|
||||
}
|
||||
}
|
||||
|
||||
.meetings-list-empty-image {
|
||||
text-align: center;
|
||||
margin: 24px 0 20px 0;
|
||||
}
|
||||
|
||||
.meetings-list-empty-button {
|
||||
align-items: center;
|
||||
color: #0163FF;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin: 24px 0 32px 0;
|
||||
}
|
||||
|
||||
.meetings-list-empty-icon {
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.button {
|
||||
background: #0074E0;
|
||||
border-radius: 4px;
|
||||
@@ -32,7 +54,7 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 5px 10px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -43,12 +65,13 @@
|
||||
}
|
||||
|
||||
.item {
|
||||
background: rgba(255,255,255,0.50);
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
display: inline-flex;
|
||||
margin-top: 5px;
|
||||
min-height: 92px;
|
||||
width: 100%;
|
||||
margin: 4px 4px 0 4px;
|
||||
min-height: 60px;
|
||||
width: calc(100% - 8px);
|
||||
word-break: break-word;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -61,37 +84,41 @@
|
||||
.left-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 140px;
|
||||
flex-grow: 0;
|
||||
padding-left: 30px;
|
||||
padding-top: 25px;
|
||||
|
||||
.date {
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
padding-left: 16px;
|
||||
padding-top: 13px;
|
||||
}
|
||||
|
||||
.right-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
padding-left: 30px;
|
||||
padding-top: 25px;
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
padding-left: 16px;
|
||||
padding-top: 13px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #5E6D7A;
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-grow: 0;
|
||||
padding-right: 30px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
&.with-click-handler {
|
||||
@@ -99,7 +126,7 @@
|
||||
}
|
||||
|
||||
&.with-click-handler:hover {
|
||||
background-color: #75A7E7;
|
||||
background-color: #c7ddff;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
@@ -120,4 +147,20 @@
|
||||
display: block
|
||||
}
|
||||
}
|
||||
|
||||
.delete-meeting {
|
||||
display: none;
|
||||
margin-right: 16px;
|
||||
position: absolute;
|
||||
|
||||
&> svg {
|
||||
fill: #0074e0;
|
||||
}
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
.delete-meeting {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,12 +62,15 @@
|
||||
&-status {
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
bottom: 0;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
min-height: 24px;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
|
||||
&--warning {
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding-bottom: 24px;
|
||||
z-index: $toolbarZ + 2;
|
||||
|
||||
.title {
|
||||
|
||||
@@ -30,6 +30,67 @@
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $verySmallScreen) {
|
||||
.welcome {
|
||||
#enter_room {
|
||||
position: relative;
|
||||
height: 42px;
|
||||
|
||||
.welcome-page-button {
|
||||
font-size: 16px;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 68px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #06345E;
|
||||
background-image: linear-gradient(180deg, rgba(8, 110, 202, 0.8) 0%, rgba(8, 110, 202, 0) 100%);
|
||||
|
||||
#enter_room {
|
||||
.enter-room-input-container {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.warning-without-link,
|
||||
.warning-with-link {
|
||||
top: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-tabs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header-text-title {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcome-cards-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.without-content {
|
||||
.header {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#moderated-meetings {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.welcome-footer-row-block {
|
||||
display: block;
|
||||
}
|
||||
.welcome-badge {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
#videoResolutionLabel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -161,71 +161,47 @@ $unsupportedDesktopBrowserTextFontSize: 21px;
|
||||
/**
|
||||
* The size of the default watermark.
|
||||
*/
|
||||
$watermarkWidth: 186px;
|
||||
$watermarkHeight: 74px;
|
||||
$watermarkWidth: 71px;
|
||||
$watermarkHeight: 32px;
|
||||
|
||||
$welcomePageWatermarkWidth: 186px;
|
||||
$welcomePageWatermarkHeight: 74px;
|
||||
$welcomePageWatermarkWidth: 71px;
|
||||
$welcomePageWatermarkHeight: 32px;
|
||||
|
||||
/**
|
||||
* Welcome page variables.
|
||||
*/
|
||||
$welcomePageDescriptionColor: #fff;
|
||||
$welcomePageFontFamily: inherit;
|
||||
$welcomePageBackground: linear-gradient(-90deg, #1251AE 0%, #0074FF 50%, #1251AE 100%);
|
||||
$welcomePageBackground: none;
|
||||
$welcomePageTitleColor: #fff;
|
||||
|
||||
$welcomePageHeaderBackground: none;
|
||||
$welcomePageHeaderBackgroundSmall: none;
|
||||
$welcomePageHeaderBackground: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), url('/images/welcome-background.png');
|
||||
$welcomePageHeaderBackgroundPosition: none;
|
||||
$welcomePageHeaderBackgroundRepeat: none;
|
||||
$welcomePageHeaderBackgroundSize: none;
|
||||
$welcomePageHeaderBackgroundSize: cover;
|
||||
$welcomePageHeaderPaddingBottom: 0px;
|
||||
$welcomePageHeaderMinHeight: fit-content;
|
||||
$welcomePageHeaderTitleMaxWidth: initial;
|
||||
$welcomePageHeaderTextAlign: center;
|
||||
|
||||
$welcomePageHeaderTextMarginTop: 35px;
|
||||
$welcomePageHeaderTextMarginBottom: 35px;
|
||||
$welcomePageHeaderTextDisplay: flex;
|
||||
$welcomePageHeaderTextWidth: 650px;
|
||||
$welcomePageHeaderContainerDisplay: flex;
|
||||
$welcomePageHeaderContainerMargin: 146px 32px 0 32px;
|
||||
|
||||
$welcomePageHeaderTextTitleMarginBottom: 16px;
|
||||
$welcomePageHeaderTextTitleFontSize: 2.5rem;
|
||||
$welcomePageHeaderTextTitleFontWeight: 500;
|
||||
$welcomePageHeaderTextTitleLineHeight: 1.18;
|
||||
$welcomePageHeaderTextTitleMarginBottom: 0;
|
||||
$welcomePageHeaderTextTitleFontSize: 42px;
|
||||
$welcomePageHeaderTextTitleFontWeight: normal;
|
||||
$welcomePageHeaderTextTitleLineHeight: 50px;
|
||||
$welcomePageHeaderTextTitleOpacity: 1;
|
||||
|
||||
$welcomePageHeaderTextDescriptionDisplay: inherit;
|
||||
$welcomePageHeaderTextDescriptionFontSize: 1rem;
|
||||
$welcomePageHeaderTextDescriptionFontWeight: 400;
|
||||
$welcomePageHeaderTextDescriptionLineHeight: 24px;
|
||||
$welcomePageHeaderTextDescriptionMarginBottom: 20px;
|
||||
$welcomePageHeaderTextDescriptionAlignSelf: inherit;
|
||||
|
||||
$welcomePageEnterRoomDisplay: flex;
|
||||
$welcomePageEnterRoomWidth: 680px;
|
||||
$welcomePageEnterRoomPadding: 25px 30px;
|
||||
$welcomePageEnterRoomBorderRadius: 0px;
|
||||
|
||||
$welcomePageEnterRoomInputContainerPadding: 0 8px 5px 0px;
|
||||
$welcomePageEnterRoomInputContainerBorderWidth: 0px 0px 2px 0px;
|
||||
$welcomePageEnterRoomInputContainerBorderStyle: solid;
|
||||
$welcomePageEnterRoomInputContainerBorderImage: linear-gradient(to right, #dee1e6, #fff) 1;
|
||||
|
||||
$welcomePageEnterRoomTitleDisplay: inherit;
|
||||
$welcomePageEnterRoomWidth: calc(100% - 32px);
|
||||
$welcomePageEnterRoomPadding: 4px;
|
||||
$welcomePageEnterRoomMargin: 0 auto;
|
||||
|
||||
$welcomePageTabContainerDisplay: flex;
|
||||
$welcomePageTabContentDisplay: inherit;
|
||||
$welcomePageTabButtonsDisplay: flex;
|
||||
$welcomePageTabDisplay: block;
|
||||
|
||||
$welcomePageButtonWidth: 51px;
|
||||
$welcomePageButtonMinWidth: inherit;
|
||||
$welcomePageButtonFontSize: 14px;
|
||||
$welcomePageButtonHeight: 35px;
|
||||
$welcomePageButtonFontWeight: inherit;
|
||||
$welcomePageButtonBorderRadius: 4px;
|
||||
$welcomePageButtonLineHeight: 35px;
|
||||
|
||||
/**
|
||||
* Deep-linking page variables.
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@ body.welcome-page {
|
||||
|
||||
.welcome {
|
||||
background-image: $welcomePageBackground;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: $welcomePageFontFamily;
|
||||
@@ -18,21 +19,15 @@ body.welcome-page {
|
||||
background-repeat: $welcomePageHeaderBackgroundRepeat;
|
||||
background-size: $welcomePageHeaderBackgroundSize;
|
||||
padding-bottom: $welcomePageHeaderPaddingBottom;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: $welcomePageHeaderMinHeight;
|
||||
background-color: #002637;
|
||||
height: 480px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
.header-text {
|
||||
display: $welcomePageHeaderTextDisplay;
|
||||
.header-container {
|
||||
display: $welcomePageHeaderContainerDisplay;
|
||||
flex-direction: column;
|
||||
margin-top: $watermarkHeight + $welcomePageHeaderTextMarginTop;
|
||||
margin-bottom: $welcomePageHeaderTextMarginBottom;
|
||||
max-width: calc(100% - 40px);
|
||||
width: $welcomePageHeaderTextWidth;
|
||||
margin: $welcomePageHeaderContainerMargin;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
@@ -42,50 +37,52 @@ body.welcome-page {
|
||||
font-weight: $welcomePageHeaderTextTitleFontWeight;
|
||||
line-height: $welcomePageHeaderTextTitleLineHeight;
|
||||
margin-bottom: $welcomePageHeaderTextTitleMarginBottom;
|
||||
max-width: $welcomePageHeaderTitleMaxWidth;
|
||||
opacity: $welcomePageHeaderTextTitleOpacity;
|
||||
text-align: $welcomePageHeaderTextAlign;
|
||||
}
|
||||
|
||||
.header-text-description {
|
||||
display: $welcomePageHeaderTextDescriptionDisplay;
|
||||
color: $welcomePageDescriptionColor;
|
||||
font-size: $welcomePageHeaderTextDescriptionFontSize;
|
||||
font-weight: $welcomePageHeaderTextDescriptionFontWeight;
|
||||
line-height: $welcomePageHeaderTextDescriptionLineHeight;
|
||||
margin-bottom: $welcomePageHeaderTextDescriptionMarginBottom;
|
||||
align-self: $welcomePageHeaderTextDescriptionAlignSelf;
|
||||
.header-text-subtitle {
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 26px;
|
||||
margin: 16px 0 32px 0;
|
||||
text-align: $welcomePageHeaderTextAlign;
|
||||
|
||||
}
|
||||
|
||||
#enter_room {
|
||||
display: $welcomePageEnterRoomDisplay;
|
||||
align-items: center;
|
||||
max-width: calc(100% - 40px);
|
||||
max-width: 480px;
|
||||
width: $welcomePageEnterRoomWidth;
|
||||
z-index: $zindex2;
|
||||
background-color: #fff;
|
||||
padding: $welcomePageEnterRoomPadding;
|
||||
border-radius: $welcomePageEnterRoomBorderRadius;
|
||||
border-radius: 4px;
|
||||
margin: $welcomePageEnterRoomMargin;
|
||||
|
||||
.enter-room-input-container {
|
||||
width: 100%;
|
||||
padding: $welcomePageEnterRoomInputContainerPadding;
|
||||
text-align: left;
|
||||
color: #253858;
|
||||
flex-grow: 1;
|
||||
height: fit-content;
|
||||
|
||||
.enter-room-title {
|
||||
display: $welcomePageEnterRoomTitleDisplay;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
padding-right: 4px;
|
||||
position: relative;
|
||||
|
||||
.enter-room-input {
|
||||
border-width: $welcomePageEnterRoomInputContainerBorderWidth;
|
||||
border-style: $welcomePageEnterRoomInputContainerBorderStyle;
|
||||
border-image: $welcomePageEnterRoomInputContainerBorderImage;
|
||||
border: 0;
|
||||
background: #fff;
|
||||
display: inline-block;
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
padding-left: 10px;
|
||||
|
||||
&:focus {
|
||||
outline: auto 2px #005fcc;
|
||||
}
|
||||
}
|
||||
|
||||
.insecure-room-name-warning {
|
||||
@@ -109,16 +106,28 @@ body.welcome-page {
|
||||
}
|
||||
}
|
||||
|
||||
.warning-without-link {
|
||||
position: absolute;
|
||||
top: 44px;
|
||||
left: -10px;
|
||||
}
|
||||
|
||||
.warning-with-link {
|
||||
position: absolute;
|
||||
top: 84px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#moderated-meetings {
|
||||
max-width: calc(100% - 40px);
|
||||
padding: 16px 0 39px 0;
|
||||
margin: $welcomePageEnterRoomMargin;
|
||||
width: $welcomePageEnterRoomWidth;
|
||||
|
||||
p {
|
||||
color: $welcomePageDescriptionColor;
|
||||
text-align: left;
|
||||
text-align: $welcomePageHeaderTextAlign;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
@@ -126,76 +135,70 @@ body.welcome-page {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
font-size: 16px;
|
||||
.tab-container {
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
display: $welcomePageTabContainerDisplay;
|
||||
flex-direction: column;
|
||||
|
||||
.tab-content{
|
||||
display: $welcomePageTabContentDisplay;
|
||||
height: 250px;
|
||||
margin: 5px 0px;
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
min-height: 354px;
|
||||
width: 710px;
|
||||
background: #75A7E7;
|
||||
display: $welcomePageTabContainerDisplay;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tab-content{
|
||||
display: $welcomePageTabContentDisplay;
|
||||
margin: 5px 0px;
|
||||
overflow: hidden;
|
||||
.tab-buttons {
|
||||
background-color: #c7ddff;
|
||||
border-radius: 6px;
|
||||
color: #0163FF;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin: 4px;
|
||||
display: $welcomePageTabButtonsDisplay;
|
||||
|
||||
.tab {
|
||||
background-color: #c7ddff;
|
||||
border-radius: 7px;
|
||||
cursor: pointer;
|
||||
display: $welcomePageTabDisplay;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
margin: 2px;
|
||||
padding: 7px 0;
|
||||
text-align: center;
|
||||
|
||||
> * {
|
||||
position: absolute;
|
||||
&.selected {
|
||||
background-color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-buttons {
|
||||
font-size: 18px;
|
||||
color: #FFFFFF;
|
||||
display: $welcomePageTabButtonsDisplay;
|
||||
flex-grow: 0;
|
||||
flex-direction: row;
|
||||
min-height: 54px;
|
||||
width: 100%;
|
||||
|
||||
.tab {
|
||||
display: $welcomePageTabDisplay;
|
||||
text-align: center;
|
||||
background: rgba(9,30,66,0.37);
|
||||
height: 55px;
|
||||
line-height: 54px;
|
||||
flex-grow: 1;
|
||||
cursor: pointer;
|
||||
|
||||
&.selected, &:hover {
|
||||
background: rgba(9,30,66,0.71);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-left: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-page-button {
|
||||
width: $welcomePageButtonWidth;
|
||||
min-width: $welcomePageButtonMinWidth;
|
||||
height: $welcomePageButtonHeight;
|
||||
font-size: $welcomePageButtonFontSize;
|
||||
font-weight: $welcomePageButtonFontWeight;
|
||||
border: 0;
|
||||
font-size: 14px;
|
||||
background: #0074E0;
|
||||
border-radius: $welcomePageButtonBorderRadius;
|
||||
border-radius: 3px;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: $welcomePageButtonLineHeight;
|
||||
cursor: pointer;
|
||||
padding: 16px 20px;
|
||||
|
||||
&:focus-within {
|
||||
outline: auto 2px #022e61;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-page-settings {
|
||||
background: rgba(255, 255, 255, 0.38);
|
||||
border-radius: 3px;
|
||||
color: $welcomePageDescriptionColor;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
right: 32px;
|
||||
@@ -217,4 +220,83 @@ body.welcome-page {
|
||||
height: $welcomePageWatermarkHeight;
|
||||
}
|
||||
}
|
||||
|
||||
&.without-content {
|
||||
.welcome-card {
|
||||
min-width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-cards-container {
|
||||
color:#131519;
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
.welcome-card-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0 32px;
|
||||
}
|
||||
|
||||
.welcome-card-text {
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.welcome-card {
|
||||
width: 49%;
|
||||
border-radius: 8px;
|
||||
|
||||
&--dark {
|
||||
background: #444447;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&--blue {
|
||||
background: #D5E5FF;
|
||||
}
|
||||
|
||||
&--grey {
|
||||
background: #F2F3F4;
|
||||
}
|
||||
|
||||
&--shadow {
|
||||
box-shadow: 0px 4px 30px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-footer {
|
||||
background: #131519;
|
||||
color: #fff;
|
||||
margin-top: 40px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.welcome-footer-centered {
|
||||
max-width: 688px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.welcome-footer-padded {
|
||||
padding: 0px 16px;
|
||||
}
|
||||
|
||||
.welcome-footer-row-block {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #424447;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-footer--row-1 {
|
||||
padding: 40px 0 24px 0;
|
||||
}
|
||||
|
||||
.welcome-footer-row-1-text {
|
||||
max-width: 200px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
2
debian/jitsi-meet-web.install
vendored
@@ -13,3 +13,5 @@ lang /usr/share/jitsi-meet/
|
||||
connection_optimization /usr/share/jitsi-meet/
|
||||
resources/robots.txt /usr/share/jitsi-meet/
|
||||
resources/*.sh /usr/share/jitsi-meet/scripts/
|
||||
pwa-worker.js /usr/share/jitsi-meet/
|
||||
manifest.json /usr/share/jitsi-meet/
|
||||
|
||||
BIN
images/app-store-badge.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
21
images/calendar.svg
Normal file
@@ -0,0 +1,21 @@
|
||||
<svg width="68" height="72" viewBox="0 0 68 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1" y="5.64514" width="65.3548" height="65.3548" rx="7" stroke="#A4B8D1" stroke-width="2"/>
|
||||
<rect y="23.2258" width="67.3548" height="2.0213" fill="#A4B8D1"/>
|
||||
<rect x="14.5161" width="2.32258" height="14.5161" fill="#A4B8D1"/>
|
||||
<rect x="11.6129" y="12.1935" width="8.12903" height="2.32258" fill="#A4B8D1"/>
|
||||
<rect x="50.5161" width="2.32258" height="14.5161" fill="#A4B8D1"/>
|
||||
<rect x="47.6129" y="12.1935" width="8.12903" height="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="24.387" cy="37.7419" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="33.6774" cy="37.7419" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="42.9677" cy="37.7419" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="52.258" cy="37.7419" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="24.387" cy="47.0322" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="15.0968" cy="47.0322" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="33.6774" cy="47.0322" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="42.9677" cy="47.0322" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="24.387" cy="56.3226" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="15.0968" cy="56.3226" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="33.6774" cy="56.3226" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="42.9677" cy="56.3226" r="2.32258" fill="#A4B8D1"/>
|
||||
<circle cx="52.258" cy="47.0322" r="2.32258" fill="#A4B8D1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
BIN
images/f-droid-badge.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
images/google-play-badge.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 33 KiB |
8
images/watermark.svg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
images/welcome-background.png
Normal file
|
After Width: | Height: | Size: 290 KiB |
28
index.html
@@ -4,13 +4,18 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#2A3A4B">
|
||||
<!--#include virtual="base.html" -->
|
||||
|
||||
<link rel="apple-touch-icon" href="images/apple-touch-icon.png">
|
||||
<link rel="stylesheet" href="css/all.css">
|
||||
<link rel="manifest" href="static/pwa/manifest.json">
|
||||
<link rel="manifest" id="manifest-placeholder">
|
||||
|
||||
<script>
|
||||
// Dynamically generate the manifest location URL. It must be served from the document origin, and we may have
|
||||
// the base pointing to the CDN. This way we can generate a full URL which will bypass the base.
|
||||
document.querySelector('#manifest-placeholder').setAttribute('href', window.location.origin + '/manifest.json');
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!JitsiMeetJS.app) {
|
||||
return;
|
||||
@@ -19,7 +24,21 @@
|
||||
JitsiMeetJS.app.renderEntryPoint({
|
||||
Component: JitsiMeetJS.app.entryPoints.APP
|
||||
})
|
||||
})
|
||||
|
||||
const isElectron = navigator.userAgent.includes('Electron');
|
||||
const shouldRegisterWorker = !isElectron && 'serviceWorker' in navigator;
|
||||
|
||||
if (shouldRegisterWorker) {
|
||||
navigator.serviceWorker
|
||||
.register(window.location.origin + '/pwa-worker.js')
|
||||
.then(reg => {
|
||||
console.log('Service worker registered.', reg);
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
// IE11 and earlier can be identified via their user agent and be
|
||||
@@ -160,13 +179,16 @@
|
||||
<script><!--#include virtual="/logging_config.js" --></script>
|
||||
<script src="libs/lib-jitsi-meet.min.js?v=139"></script>
|
||||
<script src="libs/app.bundle.min.js?v=139"></script>
|
||||
<script src="static/pwa/registrator.js" async></script>
|
||||
<!--#include virtual="title.html" -->
|
||||
<!--#include virtual="plugin.head.html" -->
|
||||
<!--#include virtual="static/welcomePageAdditionalContent.html" -->
|
||||
<!--#include virtual="static/welcomePageAdditionalCard.html" -->
|
||||
<!--#include virtual="static/settingsToolbarAdditionalContent.html" -->
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<div>JavaScript is disabled. </br>For this site to work you have to enable JavaScript.</div>
|
||||
</noscript>
|
||||
<!--#include virtual="body.html" -->
|
||||
<div id="react"></div>
|
||||
</body>
|
||||
|
||||
@@ -46,9 +46,9 @@ var interfaceConfig = {
|
||||
|
||||
DEFAULT_BACKGROUND: '#474747',
|
||||
DEFAULT_LOCAL_DISPLAY_NAME: 'me',
|
||||
DEFAULT_LOGO_URL: 'images/watermark.png',
|
||||
DEFAULT_LOGO_URL: 'images/watermark.svg',
|
||||
DEFAULT_REMOTE_DISPLAY_NAME: 'Fellow Jitster',
|
||||
DEFAULT_WELCOME_PAGE_LOGO_URL: 'images/watermark.png',
|
||||
DEFAULT_WELCOME_PAGE_LOGO_URL: 'images/watermark.svg',
|
||||
|
||||
DISABLE_DOMINANT_SPEAKER_INDICATOR: false,
|
||||
|
||||
@@ -86,7 +86,9 @@ var interfaceConfig = {
|
||||
*/
|
||||
DISABLE_VIDEO_BACKGROUND: false,
|
||||
|
||||
DISPLAY_WELCOME_PAGE_CONTENT: true,
|
||||
DISPLAY_WELCOME_FOOTER: true,
|
||||
DISPLAY_WELCOME_PAGE_ADDITIONAL_CARD: false,
|
||||
DISPLAY_WELCOME_PAGE_CONTENT: false,
|
||||
DISPLAY_WELCOME_PAGE_TOOLBAR_ADDITIONAL_CONTENT: false,
|
||||
|
||||
ENABLE_DIAL_OUT: true,
|
||||
@@ -136,6 +138,21 @@ var interfaceConfig = {
|
||||
*/
|
||||
MOBILE_APP_PROMO: true,
|
||||
|
||||
/**
|
||||
* Specify custom URL for downloading android mobile app.
|
||||
*/
|
||||
MOBILE_DOWNLOAD_LINK_ANDROID: 'https://play.google.com/store/apps/details?id=org.jitsi.meet',
|
||||
|
||||
/**
|
||||
* Specify custom URL for downloading f droid app.
|
||||
*/
|
||||
MOBILE_DOWNLOAD_LINK_F_DROID: 'https://f-droid.org/en/packages/org.jitsi.meet/',
|
||||
|
||||
/**
|
||||
* Specify URL for downloading ios mobile app.
|
||||
*/
|
||||
MOBILE_DOWNLOAD_LINK_IOS: 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905',
|
||||
|
||||
NATIVE_APP_NAME: 'Jitsi Meet',
|
||||
|
||||
// Names of browsers which should show a warning stating the current browser
|
||||
@@ -169,7 +186,6 @@ var interfaceConfig = {
|
||||
SHOW_JITSI_WATERMARK: true,
|
||||
SHOW_POWERED_BY: false,
|
||||
SHOW_PROMOTIONAL_CLOSE_PAGE: false,
|
||||
SHOW_WATERMARK_FOR_GUESTS: true, // if watermark is disabled by default, it can be shown only for guests
|
||||
|
||||
/*
|
||||
* If indicated some of the error dialogs may point to the support URL for
|
||||
@@ -223,27 +239,12 @@ var interfaceConfig = {
|
||||
*/
|
||||
VIDEO_QUALITY_LABEL_DISABLED: false,
|
||||
|
||||
/**
|
||||
* When enabled, the kick participant button will not be presented for users without a JWT
|
||||
*/
|
||||
// HIDE_KICK_BUTTON_FOR_GUESTS: false,
|
||||
|
||||
/**
|
||||
* How many columns the tile view can expand to. The respected range is
|
||||
* between 1 and 5.
|
||||
*/
|
||||
// TILE_VIEW_MAX_COLUMNS: 5,
|
||||
|
||||
/**
|
||||
* Specify custom URL for downloading android mobile app.
|
||||
*/
|
||||
// MOBILE_DOWNLOAD_LINK_ANDROID: 'https://play.google.com/store/apps/details?id=org.jitsi.meet',
|
||||
|
||||
/**
|
||||
* Specify URL for downloading ios mobile app.
|
||||
*/
|
||||
// MOBILE_DOWNLOAD_LINK_IOS: 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905',
|
||||
|
||||
/**
|
||||
* Specify Firebase dynamic link properties for the mobile apps.
|
||||
*/
|
||||
|
||||
@@ -293,8 +293,8 @@ PODS:
|
||||
- React
|
||||
- react-native-splash-screen (3.2.0):
|
||||
- React
|
||||
- react-native-webrtc (1.84.0):
|
||||
- React
|
||||
- react-native-webrtc (1.84.1):
|
||||
- React-Core
|
||||
- react-native-webview (10.9.0):
|
||||
- React
|
||||
- React-RCTActionSheet (0.61.5-jitsi.2):
|
||||
@@ -562,7 +562,7 @@ SPEC CHECKSUMS:
|
||||
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
|
||||
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
|
||||
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
|
||||
react-native-webrtc: 9268ae9a2bc9730796b0968d012327e92c392adf
|
||||
react-native-webrtc: edd689b0d5a462d7a6f6f52bca3f9414fc0ee11c
|
||||
react-native-webview: 6ee7868ca8eba635dbf7963986d1ab7959da0391
|
||||
React-RCTActionSheet: bcbc311dc3b47bc8efb2737ff0940239a45789a9
|
||||
React-RCTAnimation: 65f61080ce632f6dea23d52e354ffac9948396c6
|
||||
|
||||
@@ -730,7 +730,7 @@
|
||||
"stopScreenSharing": "Arrêter le partage d'écran",
|
||||
"stopSubtitles": "Désactiver les sous-titres",
|
||||
"stopSharedVideo": "Arrêter la vidéo YouTube",
|
||||
"talkWhileMutedPopup": "Vous voulez parler ? Vôtre micro est coupé.",
|
||||
"talkWhileMutedPopup": "Vous voulez parler ? Votre micro est coupé.",
|
||||
"tileViewToggle": "Activer/désactiver la vue mosaïque",
|
||||
"toggleCamera": "Changer de caméra",
|
||||
"videomute": "Démarrer / Arrêter la caméra",
|
||||
@@ -769,7 +769,7 @@
|
||||
"errorAlreadyInvited": "{{displayName}} est déjà invité(e)",
|
||||
"errorInvite": "La conférence n'est pas encore établie. Veuillez réessayer plus tard.",
|
||||
"errorInviteFailed": "Nous tentons de résoudre le problème. Veuillez réessayer plus tard.",
|
||||
"errorInviteFailedTitle": "l'invitation de {{displayName}} a échoué",
|
||||
"errorInviteFailedTitle": "L'invitation de {{displayName}} a échoué",
|
||||
"errorInviteTitle": "Erreur lors de l'invitation",
|
||||
"pending": "{{displayName}} a été invité(e)"
|
||||
},
|
||||
@@ -809,7 +809,7 @@
|
||||
"join": "Touchez pour rejoindre",
|
||||
"roomname": "Saisissez un nom de salle"
|
||||
},
|
||||
"appDescription": "Foncez tchater en vidéo avec toute le monde. En fait, vous pouvez inviter tout ceux que vous connaissez. {{app}} est une solution de visioconférence entièrement chiffrée et 100% libre que vous pouvez utiliser en permanence, chaque jours, et sans aucun compte requis.",
|
||||
"appDescription": "Foncez tchater en vidéo avec toute le monde. En fait, vous pouvez inviter tout ceux que vous connaissez. {{app}} est une solution de visioconférence entièrement chiffrée et 100% libre que vous pouvez utiliser en permanence, chaque jour, et sans aucun compte requis.",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "Voix",
|
||||
"video": "Vidéo"
|
||||
@@ -847,10 +847,10 @@
|
||||
"allow": "Autoriser",
|
||||
"backToKnockModeButton": "Aucun mot de passe, demander à rejoindre plutôt",
|
||||
"dialogTitle": "Mode lobby",
|
||||
"disableDialogContent": "Le mode lobby est actuellement activé. Cette fonctionnalité garantit que les participants indésirables ne peuvent pas rejoindre votre réunion. Souhaitez-vous la désactiver?",
|
||||
"disableDialogContent": "Le mode lobby est actuellement activé. Cette fonctionnalité garantit que les participants indésirables ne peuvent pas rejoindre votre réunion. Souhaitez-vous la désactiver ?",
|
||||
"disableDialogSubmit": "Désactiver",
|
||||
"emailField": "Saisissez votre adresse email",
|
||||
"enableDialogPasswordField": "Définir le mot de passe (optionel)",
|
||||
"enableDialogPasswordField": "Définir le mot de passe (optionnel)",
|
||||
"enableDialogSubmit": "Activer",
|
||||
"enableDialogText": "Le mode lobby vous permet de protéger votre réunion en autorisant les personnes à entrer qu'après l'approbation formelle d'un modérateur.",
|
||||
"enterPasswordButton": "Saisissez un mot de passe de réunion",
|
||||
|
||||
@@ -512,6 +512,12 @@
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Puxe para atualizar"
|
||||
},
|
||||
"security": {
|
||||
"about": "Voce pode adicionar uma $t(lockRoomPassword) em sua reunião. Participantes irão precisar informar a $t(lockRoomPassword) antes de se juntarem na reunião.",
|
||||
"aboutReadOnly": "Moderadores podem adicionar uma $t(lockRoomPassword) na reunião. Participantes irão precisar informar a $t(lockRoomPassword) antes de se juntarem na reunião",
|
||||
"insecureRoomNameWarning": "Essa sala não está protegida. Participantes indesejados poderão entrar na sua reunião. Considere configurar a segurança da sua reunião utilizando o botão de segurança.",
|
||||
"securityOptions": "Opções de segurança"
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
"about": "A integração do calendário {{appName}} é usada para acessar com segurança o seu calendário para que ele possa ler os próximos eventos.",
|
||||
|
||||
@@ -220,7 +220,6 @@
|
||||
"kickTitle": "Ouch! {{participantDisplayName}} kicked you out of the meeting",
|
||||
"liveStreaming": "Live Streaming",
|
||||
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Not possible while recording is active",
|
||||
"liveStreamingDisabledForGuestTooltip": "Guests can't start live streaming.",
|
||||
"liveStreamingDisabledTooltip": "Start live stream disabled.",
|
||||
"lockMessage": "Failed to lock the conference.",
|
||||
"lockRoom": "Add meeting $t(lockRoomPasswordUppercase)",
|
||||
@@ -255,7 +254,6 @@
|
||||
"readMore": "more",
|
||||
"recording": "Recording",
|
||||
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Not possible while a live stream is active",
|
||||
"recordingDisabledForGuestTooltip": "Guests can't start recordings.",
|
||||
"recordingDisabledTooltip": "Start recording disabled.",
|
||||
"rejoinNow": "Rejoin now",
|
||||
"remoteControlAllowedMessage": "{{user}} accepted your remote control request!",
|
||||
@@ -287,7 +285,6 @@
|
||||
"shareVideoTitle": "Share a video",
|
||||
"shareYourScreen": "Share your screen",
|
||||
"shareYourScreenDisabled": "Screen sharing disabled.",
|
||||
"shareYourScreenDisabledForGuest": "Guests can't screen share.",
|
||||
"startLiveStreaming": "Start live stream",
|
||||
"startRecording": "Start recording",
|
||||
"startRemoteControlErrorMessage": "An error occurred while trying to start the remote control session!",
|
||||
@@ -878,6 +875,8 @@
|
||||
"goSmall": "GO",
|
||||
"info": "Dial-in info",
|
||||
"join": "CREATE / JOIN",
|
||||
"jitsiMeet": "Jitsi Meet",
|
||||
"jitsiOnMobile": "Jitsi on mobile – download our apps and start a meeting from anywhere",
|
||||
"moderatedMessage": "Or <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">book a meeting URL</a> in advance where you are the only moderator.",
|
||||
"privacy": "Privacy",
|
||||
"recentList": "Recent",
|
||||
@@ -888,6 +887,8 @@
|
||||
"roomname": "Enter room name",
|
||||
"roomnameHint": "Enter the name or URL of the room you want to join. You may make a name up, just let the people you are meeting know it so that they enter the same name.",
|
||||
"sendFeedback": "Send feedback",
|
||||
"secureMeetings": "Secure and high quality meetings",
|
||||
"startMeeting": "Start meeting",
|
||||
"terms": "Terms",
|
||||
"title": "Secure, fully featured, and completely free video conferencing"
|
||||
},
|
||||
|
||||
@@ -11,14 +11,20 @@
|
||||
"name": "Jitsi Meet",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icons/icon192.png",
|
||||
"src": "static/pwa/icons/icon192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon512.png",
|
||||
"src": "static/pwa/icons/icon512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
},
|
||||
{
|
||||
"src": "static/pwa/icons/iconMask.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import Logger from 'jitsi-meet-logger';
|
||||
|
||||
import * as JitsiMeetConferenceEvents from '../../ConferenceEvents';
|
||||
import {
|
||||
createApiEvent,
|
||||
sendAnalytics
|
||||
@@ -14,7 +13,7 @@ import {
|
||||
setSubject
|
||||
} from '../../react/features/base/conference';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
|
||||
import { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
|
||||
import JitsiMeetJS, { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
|
||||
import { pinParticipant } from '../../react/features/base/participants';
|
||||
import {
|
||||
processExternalDeviceRequest
|
||||
@@ -46,14 +45,6 @@ declare var APP: Object;
|
||||
*/
|
||||
let commands = {};
|
||||
|
||||
/**
|
||||
* The state of screen sharing(started/stopped) before the screen sharing is
|
||||
* enabled and initialized.
|
||||
* NOTE: This flag help us to cache the state and use it if toggle-share-screen
|
||||
* was received before the initialization.
|
||||
*/
|
||||
let initialScreenSharingState = false;
|
||||
|
||||
/**
|
||||
* The transport instance used for communication with external apps.
|
||||
*
|
||||
@@ -223,7 +214,8 @@ function initCommands() {
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts a file recording or streaming depending on the passed on params.
|
||||
* Starts a file recording or streaming session depending on the passed on params.
|
||||
* For RTMP streams, `rtmpStreamKey` must be passed on. `rtmpBroadcastID` is optional.
|
||||
* For youtube streams, `youtubeStreamKey` must be passed on. `youtubeBroadcastID` is optional.
|
||||
* For dropbox recording, recording `mode` should be `file` and a dropbox oauth2 token must be provided.
|
||||
* For file recording, recording `mode` should be `file` and optionally `shouldShare` could be passed on.
|
||||
@@ -231,13 +223,23 @@ function initCommands() {
|
||||
*
|
||||
* @param { string } arg.mode - Recording mode, either `file` or `stream`.
|
||||
* @param { string } arg.dropboxToken - Dropbox oauth2 token.
|
||||
* @param { string } arg.rtmpStreamKey - The RTMP stream key.
|
||||
* @param { string } arg.rtmpBroadcastID - The RTMP braodcast ID.
|
||||
* @param { boolean } arg.shouldShare - Whether the recording should be shared with the participants or not.
|
||||
* Only applies to certain jitsi meet deploys.
|
||||
* @param { string } arg.youtubeStreamKey - The youtube stream key.
|
||||
* @param { string } arg.youtubeBroadcastID - The youtube broacast ID.
|
||||
* @returns {void}
|
||||
*/
|
||||
'start-recording': ({ mode, dropboxToken, shouldShare, youtubeStreamKey, youtubeBroadcastID }) => {
|
||||
'start-recording': ({
|
||||
mode,
|
||||
dropboxToken,
|
||||
shouldShare,
|
||||
rtmpStreamKey,
|
||||
rtmpBroadcastID,
|
||||
youtubeStreamKey,
|
||||
youtubeBroadcastID
|
||||
}) => {
|
||||
const state = APP.store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
@@ -253,8 +255,8 @@ function initCommands() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === JitsiRecordingConstants.mode.STREAM && !youtubeStreamKey) {
|
||||
logger.error('Failed starting recording: missing youtube stream key');
|
||||
if (mode === JitsiRecordingConstants.mode.STREAM && !(youtubeStreamKey || rtmpStreamKey)) {
|
||||
logger.error('Failed starting recording: missing youtube or RTMP stream key');
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -286,9 +288,9 @@ function initCommands() {
|
||||
}
|
||||
} else if (mode === JitsiRecordingConstants.mode.STREAM) {
|
||||
recordingConfig = {
|
||||
broadcastId: youtubeBroadcastID,
|
||||
broadcastId: youtubeBroadcastID || rtmpBroadcastID,
|
||||
mode: JitsiRecordingConstants.mode.STREAM,
|
||||
streamId: youtubeStreamKey
|
||||
streamId: youtubeStreamKey || rtmpStreamKey
|
||||
};
|
||||
} else {
|
||||
logger.error('Invalid recording mode provided');
|
||||
@@ -419,19 +421,6 @@ function initCommands() {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for desktop/screen sharing enabled events and toggles the screen
|
||||
* sharing if needed.
|
||||
*
|
||||
* @param {boolean} enabled - Current screen sharing enabled status.
|
||||
* @returns {void}
|
||||
*/
|
||||
function onDesktopSharingEnabledChanged(enabled = false) {
|
||||
if (enabled && initialScreenSharingState) {
|
||||
toggleScreenSharing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the API should be enabled or not.
|
||||
*
|
||||
@@ -459,12 +448,10 @@ function shouldBeEnabled() {
|
||||
* @returns {void}
|
||||
*/
|
||||
function toggleScreenSharing(enable) {
|
||||
if (APP.conference.isDesktopSharingEnabled) {
|
||||
|
||||
// eslint-disable-next-line no-empty-function
|
||||
APP.conference.toggleScreenSharing(enable).catch(() => {});
|
||||
} else {
|
||||
initialScreenSharingState = !initialScreenSharingState;
|
||||
if (JitsiMeetJS.isDesktopSharingEnabled()) {
|
||||
APP.conference.toggleScreenSharing(enable).catch(() => {
|
||||
logger.warn('Failed to toggle screen-sharing');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,10 +484,6 @@ class API {
|
||||
*/
|
||||
this._enabled = true;
|
||||
|
||||
APP.conference.addListener(
|
||||
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
|
||||
onDesktopSharingEnabledChanged);
|
||||
|
||||
initCommands();
|
||||
}
|
||||
|
||||
@@ -1047,9 +1030,6 @@ class API {
|
||||
dispose() {
|
||||
if (this._enabled) {
|
||||
this._enabled = false;
|
||||
APP.conference.removeListener(
|
||||
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
|
||||
onDesktopSharingEnabledChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
33
modules/API/external/external_api.js
vendored
@@ -1047,6 +1047,39 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
return setVideoInputDevice(this._transport, label, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a file recording or streaming session depending on the passed on params.
|
||||
* For RTMP streams, `rtmpStreamKey` must be passed on. `rtmpBroadcastID` is optional.
|
||||
* For youtube streams, `youtubeStreamKey` must be passed on. `youtubeBroadcastID` is optional.
|
||||
* For dropbox recording, recording `mode` should be `file` and a dropbox oauth2 token must be provided.
|
||||
* For file recording, recording `mode` should be `file` and optionally `shouldShare` could be passed on.
|
||||
* No other params should be passed.
|
||||
*
|
||||
* @param {Object} options - An object with config options to pass along.
|
||||
* @param { string } options.mode - Recording mode, either `file` or `stream`.
|
||||
* @param { string } options.dropboxToken - Dropbox oauth2 token.
|
||||
* @param { boolean } options.shouldShare - Whether the recording should be shared with the participants or not.
|
||||
* Only applies to certain jitsi meet deploys.
|
||||
* @param { string } options.rtmpStreamKey - The RTMP stream key.
|
||||
* @param { string } options.rtmpBroadcastID - The RTMP broacast ID.
|
||||
* @param { string } options.youtubeStreamKey - The youtube stream key.
|
||||
* @param { string } options.youtubeBroadcastID - The youtube broacast ID.
|
||||
* @returns {void}
|
||||
*/
|
||||
startRecording(options) {
|
||||
this.executeCommand('startRecording', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops a recording or streaming session that is in progress.
|
||||
*
|
||||
* @param {string} mode - `file` or `stream`.
|
||||
* @returns {void}
|
||||
*/
|
||||
stopRecording(mode) {
|
||||
this.executeCommand('startRecording', mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration for electron for the windows that are open
|
||||
* from Jitsi Meet.
|
||||
|
||||
@@ -356,8 +356,8 @@ UI.askForNickname = function() {
|
||||
/**
|
||||
* Sets muted audio state for participant
|
||||
*/
|
||||
UI.setAudioMuted = function(id, muted) {
|
||||
VideoLayout.onAudioMute(id, muted);
|
||||
UI.setAudioMuted = function(id) {
|
||||
// FIXME: Maybe this can be removed!
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
APP.conference.updateAudioIconEnabled();
|
||||
}
|
||||
@@ -366,8 +366,8 @@ UI.setAudioMuted = function(id, muted) {
|
||||
/**
|
||||
* Sets muted video state for participant
|
||||
*/
|
||||
UI.setVideoMuted = function(id, muted) {
|
||||
VideoLayout.onVideoMute(id, muted);
|
||||
UI.setVideoMuted = function(id) {
|
||||
VideoLayout.onVideoMute(id);
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
APP.conference.updateVideoIconEnabled();
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ export default class SharedVideoThumb extends SmallVideo {
|
||||
this.$container = $(this.container);
|
||||
this._setThumbnailSize();
|
||||
this.bindHoverHandler();
|
||||
this.isVideoMuted = true;
|
||||
this.updateDisplayName();
|
||||
this.container.onclick = this._onContainerClick;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
JitsiParticipantConnectionStatus
|
||||
} from '../../../react/features/base/lib-jitsi-meet';
|
||||
import { VIDEO_TYPE } from '../../../react/features/base/media';
|
||||
import { getParticipantById } from '../../../react/features/base/participants';
|
||||
import { CHAT_SIZE } from '../../../react/features/chat';
|
||||
import {
|
||||
updateKnownLargeVideoResolution
|
||||
@@ -224,9 +225,8 @@ export default class LargeVideoManager {
|
||||
const wasUsersImageCached
|
||||
= !isUserSwitch && container.wasVideoRendered;
|
||||
const isVideoMuted = !stream || stream.isMuted();
|
||||
|
||||
const connectionStatus
|
||||
= APP.conference.getParticipantConnectionStatus(id);
|
||||
const participant = getParticipantById(APP.store.getState(), id);
|
||||
const connectionStatus = participant?.connectionStatus;
|
||||
const isVideoRenderable
|
||||
= !isVideoMuted
|
||||
&& (APP.conference.isLocalId(id)
|
||||
@@ -356,17 +356,12 @@ export default class LargeVideoManager {
|
||||
let widthToUse = this.preferredWidth || window.innerWidth;
|
||||
const { isOpen } = APP.store.getState()['features/chat'];
|
||||
|
||||
/**
|
||||
* If chat state is open, we re-compute the container width by subtracting the default width of
|
||||
* the chat. We re-compute the width again after the chat window is closed. This is needed when
|
||||
* custom styling is configured on the large video container through the iFrame API.
|
||||
*/
|
||||
if (isOpen && !this.resizedForChat) {
|
||||
if (isOpen) {
|
||||
/**
|
||||
* If chat state is open, we re-compute the container width
|
||||
* by subtracting the default width of the chat.
|
||||
*/
|
||||
widthToUse -= CHAT_SIZE;
|
||||
this.resizedForChat = true;
|
||||
} else if (this.resizedForChat) {
|
||||
this.resizedForChat = false;
|
||||
widthToUse += CHAT_SIZE;
|
||||
}
|
||||
|
||||
this.width = widthToUse;
|
||||
@@ -484,8 +479,8 @@ export default class LargeVideoManager {
|
||||
*/
|
||||
showRemoteConnectionMessage(show) {
|
||||
if (typeof show !== 'boolean') {
|
||||
const connStatus
|
||||
= APP.conference.getParticipantConnectionStatus(this.id);
|
||||
const participant = getParticipantById(APP.store.getState(), this.id);
|
||||
const connStatus = participant?.connectionStatus;
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
show = !APP.conference.isLocalId(this.id)
|
||||
|
||||
@@ -63,6 +63,7 @@ export default class LocalVideo extends SmallVideo {
|
||||
|
||||
this.addAudioLevelIndicator();
|
||||
this.updateIndicators();
|
||||
this.updateStatusBar();
|
||||
|
||||
this.container.onclick = this._onContainerClick;
|
||||
}
|
||||
@@ -103,7 +104,7 @@ export default class LocalVideo extends SmallVideo {
|
||||
}
|
||||
|
||||
this._renderDisplayName({
|
||||
allowEditing: APP.store.getState()['features/base/jwt'].isGuest,
|
||||
allowEditing: !config.disableProfile,
|
||||
displayNameSuffix: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME,
|
||||
elementID: 'localDisplayName',
|
||||
participantID: this.id
|
||||
|
||||
@@ -12,10 +12,13 @@ import { i18next } from '../../../react/features/base/i18n';
|
||||
import {
|
||||
JitsiParticipantConnectionStatus
|
||||
} from '../../../react/features/base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../../../react/features/base/media';
|
||||
import {
|
||||
getParticipantById,
|
||||
getPinnedParticipant,
|
||||
pinParticipant
|
||||
} from '../../../react/features/base/participants';
|
||||
import { isRemoteTrackMuted } from '../../../react/features/base/tracks';
|
||||
import { PresenceLabel } from '../../../react/features/presence-status';
|
||||
import {
|
||||
REMOTE_CONTROL_MENU_STATES,
|
||||
@@ -86,7 +89,6 @@ export default class RemoteVideo extends SmallVideo {
|
||||
this.bindHoverHandler();
|
||||
this.flipX = false;
|
||||
this.isLocal = false;
|
||||
this.popupMenuIsHovered = false;
|
||||
this._isRemoteControlSessionActive = false;
|
||||
|
||||
/**
|
||||
@@ -98,17 +100,6 @@ export default class RemoteVideo extends SmallVideo {
|
||||
*/
|
||||
this._canPlayEventReceived = false;
|
||||
|
||||
/**
|
||||
* The flag is set to <tt>true</tt> if remote participant's video gets muted
|
||||
* during his media connection disruption. This is to prevent black video
|
||||
* being render on the thumbnail, because even though once the video has
|
||||
* been played the image usually remains on the video element it seems that
|
||||
* after longer period of the video element being hidden this image can be
|
||||
* lost.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.mutedWhileDisconnected = false;
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
// TODO The event handlers should be turned into actions so changes can be
|
||||
// handled through reducers and middleware.
|
||||
@@ -137,17 +128,6 @@ export default class RemoteVideo extends SmallVideo {
|
||||
return this.container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether current video is considered hovered. Currently it is hovered
|
||||
* if the mouse is over the video, or if the connection indicator or the popup
|
||||
* menu is shown(hovered).
|
||||
* @private
|
||||
* NOTE: extends SmallVideo's method
|
||||
*/
|
||||
_isHovered() {
|
||||
return super._isHovered() || this.popupMenuIsHovered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the popup menu content.
|
||||
*
|
||||
@@ -207,7 +187,6 @@ export default class RemoteVideo extends SmallVideo {
|
||||
<AtlasKitThemeProvider mode = 'dark'>
|
||||
<RemoteVideoMenuTriggerButton
|
||||
initialVolumeValue = { initialVolumeValue }
|
||||
isAudioMuted = { this.isAudioMuted }
|
||||
menuPosition = { remoteMenuPosition }
|
||||
onMenuDisplay
|
||||
= {this._onRemoteVideoMenuDisplay.bind(this)}
|
||||
@@ -311,43 +290,11 @@ export default class RemoteVideo extends SmallVideo {
|
||||
|
||||
/**
|
||||
* Updates the remote video menu.
|
||||
*
|
||||
* @param isMuted the new muted state to update to
|
||||
*/
|
||||
updateRemoteVideoMenu(isMuted) {
|
||||
if (typeof isMuted !== 'undefined') {
|
||||
this.isAudioMuted = isMuted;
|
||||
}
|
||||
updateRemoteVideoMenu() {
|
||||
this._generatePopupContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @override
|
||||
*/
|
||||
setVideoMutedView(isMuted) {
|
||||
super.setVideoMutedView(isMuted);
|
||||
|
||||
// Update 'mutedWhileDisconnected' flag
|
||||
this._figureOutMutedWhileDisconnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Figures out the value of {@link #mutedWhileDisconnected} flag by taking into
|
||||
* account remote participant's network connectivity and video muted status.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_figureOutMutedWhileDisconnected() {
|
||||
const isActive = this.isConnectionActive();
|
||||
|
||||
if (!isActive && this.isVideoMuted) {
|
||||
this.mutedWhileDisconnected = true;
|
||||
} else if (isActive && !this.isVideoMuted) {
|
||||
this.mutedWhileDisconnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the remote stream element corresponding to the given stream and
|
||||
* parent container.
|
||||
@@ -378,17 +325,6 @@ export default class RemoteVideo extends SmallVideo {
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the remote user associated with this <tt>RemoteVideo</tt>
|
||||
* has connectivity issues.
|
||||
*
|
||||
* @return {boolean} <tt>true</tt> if the user's connection is fine or
|
||||
* <tt>false</tt> otherwise.
|
||||
*/
|
||||
isConnectionActive() {
|
||||
return this.user.getConnectionStatus() === JitsiParticipantConnectionStatus.ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* The remote video is considered "playable" once the can play event has been received. It will be allowed to
|
||||
* display video also in {@link JitsiParticipantConnectionStatus.INTERRUPTED} if the video has received the canplay
|
||||
@@ -400,12 +336,13 @@ export default class RemoteVideo extends SmallVideo {
|
||||
* @override
|
||||
*/
|
||||
isVideoPlayable() {
|
||||
const connectionState = APP.conference.getParticipantConnectionStatus(this.id);
|
||||
const participant = getParticipantById(APP.store.getState(), this.id);
|
||||
const { connectionStatus, mutedWhileDisconnected } = participant || {};
|
||||
|
||||
return super.isVideoPlayable()
|
||||
&& this._canPlayEventReceived
|
||||
&& (connectionState === JitsiParticipantConnectionStatus.ACTIVE
|
||||
|| (connectionState === JitsiParticipantConnectionStatus.INTERRUPTED && !this.mutedWhileDisconnected));
|
||||
&& (connectionStatus === JitsiParticipantConnectionStatus.ACTIVE
|
||||
|| (connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED && !mutedWhileDisconnected));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -413,27 +350,9 @@ export default class RemoteVideo extends SmallVideo {
|
||||
*/
|
||||
updateView() {
|
||||
this.$container.toggleClass('audio-only', APP.conference.isAudioOnly());
|
||||
this.updateConnectionStatusIndicator();
|
||||
|
||||
// This must be called after 'updateConnectionStatusIndicator' because it
|
||||
// affects the display mode by modifying 'mutedWhileDisconnected' flag
|
||||
super.updateView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI to reflect user's connectivity status.
|
||||
*/
|
||||
updateConnectionStatusIndicator() {
|
||||
const connectionStatus = this.user.getConnectionStatus();
|
||||
|
||||
logger.debug(`${this.id} thumbnail connection status: ${connectionStatus}`);
|
||||
|
||||
// FIXME rename 'mutedWhileDisconnected' to 'mutedWhileNotRendering'
|
||||
// Update 'mutedWhileDisconnected' flag
|
||||
this._figureOutMutedWhileDisconnected();
|
||||
this.updateConnectionStatus(connectionStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes RemoteVideo from the page.
|
||||
*/
|
||||
|
||||
@@ -11,11 +11,19 @@ import { Provider } from 'react-redux';
|
||||
import { AudioLevelIndicator } from '../../../react/features/audio-level-indicator';
|
||||
import { Avatar as AvatarDisplay } from '../../../react/features/base/avatar';
|
||||
import { i18next } from '../../../react/features/base/i18n';
|
||||
import { MEDIA_TYPE } from '../../../react/features/base/media';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantById,
|
||||
getParticipantCount,
|
||||
getPinnedParticipant,
|
||||
pinParticipant
|
||||
} from '../../../react/features/base/participants';
|
||||
import {
|
||||
getTrackByMediaTypeAndParticipant,
|
||||
isLocalTrackMuted,
|
||||
isRemoteTrackMuted
|
||||
} from '../../../react/features/base/tracks';
|
||||
import { ConnectionIndicator } from '../../../react/features/connection-indicator';
|
||||
import { DisplayName } from '../../../react/features/display-name';
|
||||
import {
|
||||
@@ -81,34 +89,12 @@ export default class SmallVideo {
|
||||
* Constructor.
|
||||
*/
|
||||
constructor(VideoLayout) {
|
||||
this.isAudioMuted = false;
|
||||
this.isVideoMuted = false;
|
||||
this.isScreenSharing = false;
|
||||
this.videoStream = null;
|
||||
this.audioStream = null;
|
||||
this.VideoLayout = VideoLayout;
|
||||
this.videoIsHovered = false;
|
||||
this.videoType = undefined;
|
||||
|
||||
/**
|
||||
* The current state of the user's bridge connection. The value should be
|
||||
* a string as enumerated in the library's participantConnectionStatus
|
||||
* constants.
|
||||
*
|
||||
* @private
|
||||
* @type {string|null}
|
||||
*/
|
||||
this._connectionStatus = null;
|
||||
|
||||
/**
|
||||
* Whether or not the ConnectionIndicator's popover is hovered. Modifies
|
||||
* how the video overlays display based on hover state.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
this._popoverIsHovered = false;
|
||||
|
||||
/**
|
||||
* Whether or not the connection indicator should be displayed.
|
||||
*
|
||||
@@ -134,7 +120,6 @@ export default class SmallVideo {
|
||||
this._showRaisedHand = false;
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onPopoverHover = this._onPopoverHover.bind(this);
|
||||
this.updateView = this.updateView.bind(this);
|
||||
|
||||
this._onContainerClick = this._onContainerClick.bind(this);
|
||||
@@ -215,56 +200,6 @@ export default class SmallVideo {
|
||||
this.updateIndicators();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the connectionStatus stat which displays in the ConnectionIndicator.
|
||||
|
||||
* @returns {void}
|
||||
*/
|
||||
updateConnectionStatus(connectionStatus) {
|
||||
this._connectionStatus = connectionStatus;
|
||||
this.updateIndicators();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows / hides the audio muted indicator over small videos.
|
||||
*
|
||||
* @param {boolean} isMuted indicates if the muted element should be shown
|
||||
* or hidden
|
||||
*/
|
||||
showAudioIndicator(isMuted) {
|
||||
this.isAudioMuted = isMuted;
|
||||
this.updateStatusBar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows / hides the screen-share indicator over small videos.
|
||||
*
|
||||
* @param {boolean} isScreenSharing indicates if the screen-share element should be shown
|
||||
* or hidden
|
||||
*/
|
||||
setScreenSharing(isScreenSharing) {
|
||||
if (isScreenSharing === this.isScreenSharing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isScreenSharing = isScreenSharing;
|
||||
this.updateView();
|
||||
this.updateStatusBar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows video muted indicator over small videos and disables/enables avatar
|
||||
* if video muted.
|
||||
*
|
||||
* @param {boolean} isMuted indicates if we should set the view to muted view
|
||||
* or not
|
||||
*/
|
||||
setVideoMutedView(isMuted) {
|
||||
this.isVideoMuted = isMuted;
|
||||
this.updateView();
|
||||
this.updateStatusBar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or updates the ReactElement for displaying status indicators about
|
||||
* audio mute, video mute, and moderator status.
|
||||
@@ -282,9 +217,6 @@ export default class SmallVideo {
|
||||
<Provider store = { APP.store }>
|
||||
<I18nextProvider i18n = { i18next }>
|
||||
<StatusIndicators
|
||||
showAudioMutedIndicator = { this.isAudioMuted }
|
||||
showScreenShareIndicator = { this.isScreenSharing }
|
||||
showVideoMutedIndicator = { this.isVideoMuted }
|
||||
participantID = { this.id } />
|
||||
</I18nextProvider>
|
||||
</Provider>,
|
||||
@@ -459,7 +391,18 @@ export default class SmallVideo {
|
||||
* or <tt>false</tt> otherwise.
|
||||
*/
|
||||
isVideoPlayable() {
|
||||
return this.videoStream && !this.isVideoMuted && !APP.conference.isAudioOnly();
|
||||
const state = APP.store.getState();
|
||||
const tracks = state['features/base/tracks'];
|
||||
const participant = this.id ? getParticipantById(state, this.id) : getLocalParticipant(state);
|
||||
let isVideoMuted = true;
|
||||
|
||||
if (participant?.local) {
|
||||
isVideoMuted = isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO);
|
||||
} else if (!participant?.isFakeParticipant) { // remote participants excluding shared video
|
||||
isVideoMuted = isRemoteTrackMuted(tracks, MEDIA_TYPE.VIDEO, this.id);
|
||||
}
|
||||
|
||||
return this.videoStream && !isVideoMuted && !APP.conference.isAudioOnly();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -489,19 +432,32 @@ export default class SmallVideo {
|
||||
* @returns {Object}
|
||||
*/
|
||||
computeDisplayModeInput() {
|
||||
let isScreenSharing = false;
|
||||
let connectionStatus, mutedWhileDisconnected;
|
||||
const state = APP.store.getState();
|
||||
const participant = getParticipantById(state, this.id);
|
||||
|
||||
if (typeof participant !== 'undefined' && !participant.isFakeParticipant && !participant.local) {
|
||||
const tracks = state['features/base/tracks'];
|
||||
const track = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, this.id);
|
||||
|
||||
isScreenSharing = typeof track !== 'undefined' && track.videoType === 'desktop';
|
||||
connectionStatus = participant.connectionStatus;
|
||||
mutedWhileDisconnected = participant.mutedWhileDisconnected;
|
||||
}
|
||||
|
||||
return {
|
||||
isCurrentlyOnLargeVideo: this.isCurrentlyOnLargeVideo(),
|
||||
isHovered: this._isHovered(),
|
||||
isAudioOnly: APP.conference.isAudioOnly(),
|
||||
tileViewActive: shouldDisplayTileView(APP.store.getState()),
|
||||
tileViewActive: shouldDisplayTileView(state),
|
||||
isVideoPlayable: this.isVideoPlayable(),
|
||||
hasVideo: Boolean(this.selectVideoElement().length),
|
||||
connectionStatus: APP.conference.getParticipantConnectionStatus(this.id),
|
||||
mutedWhileDisconnected: this.mutedWhileDisconnected,
|
||||
connectionStatus,
|
||||
mutedWhileDisconnected,
|
||||
canPlayEventReceived: this._canPlayEventReceived,
|
||||
videoStream: Boolean(this.videoStream),
|
||||
isVideoMuted: this.isVideoMuted,
|
||||
isScreenSharing: this.isScreenSharing,
|
||||
isScreenSharing,
|
||||
videoStreamMuted: this.videoStream ? this.videoStream.isMuted() : 'no stream'
|
||||
};
|
||||
}
|
||||
@@ -513,7 +469,7 @@ export default class SmallVideo {
|
||||
* @private
|
||||
*/
|
||||
_isHovered() {
|
||||
return this.videoIsHovered || this._popoverIsHovered;
|
||||
return this.videoIsHovered;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -741,7 +697,6 @@ export default class SmallVideo {
|
||||
{ this._showConnectionIndicator
|
||||
? <ConnectionIndicator
|
||||
alwaysVisible = { showConnectionIndicator }
|
||||
connectionStatus = { this._connectionStatus }
|
||||
iconSize = { iconSize }
|
||||
isLocalVideo = { this.isLocal }
|
||||
enableStatsDisplay = { !interfaceConfig.filmStripOnly }
|
||||
@@ -836,19 +791,6 @@ export default class SmallVideo {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current state of the connection indicator popover being hovered.
|
||||
* If hovered, display the small video as if it is hovered.
|
||||
*
|
||||
* @param {boolean} popoverIsHovered - Whether or not the mouse cursor is
|
||||
* currently over the connection indicator popover.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onPopoverHover(popoverIsHovered) {
|
||||
this._popoverIsHovered = popoverIsHovered;
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the thumbnail.
|
||||
*/
|
||||
|
||||
@@ -173,11 +173,9 @@ const VideoLayout = {
|
||||
remoteVideo.addRemoteStreamElement(stream);
|
||||
|
||||
// Make sure track's muted state is reflected
|
||||
if (stream.getType() === 'audio') {
|
||||
this.onAudioMute(id, stream.isMuted());
|
||||
} else {
|
||||
this.onVideoMute(id, stream.isMuted());
|
||||
remoteVideo.setScreenSharing(stream.videoType === 'desktop');
|
||||
if (stream.getType() !== 'audio') {
|
||||
this.onVideoMute(id);
|
||||
remoteVideo.updateView();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -189,7 +187,7 @@ const VideoLayout = {
|
||||
|
||||
if (remoteVideo) {
|
||||
remoteVideo.removeRemoteStreamElement(stream);
|
||||
remoteVideo.setScreenSharing(false);
|
||||
remoteVideo.updateView();
|
||||
}
|
||||
|
||||
this.updateMutedForNoTracks(id, stream.getType());
|
||||
@@ -210,7 +208,7 @@ const VideoLayout = {
|
||||
if (mediaType === 'audio') {
|
||||
APP.UI.setAudioMuted(participantId, true);
|
||||
} else if (mediaType === 'video') {
|
||||
APP.UI.setVideoMuted(participantId, true);
|
||||
APP.UI.setVideoMuted(participantId);
|
||||
} else {
|
||||
logger.error(`Unsupported media type: ${mediaType}`);
|
||||
}
|
||||
@@ -329,35 +327,17 @@ const VideoLayout = {
|
||||
this._updateLargeVideoIfDisplayed(resourceJid, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* On audio muted event.
|
||||
*/
|
||||
onAudioMute(id, isMuted) {
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
localVideoThumbnail.showAudioIndicator(isMuted);
|
||||
} else {
|
||||
const remoteVideo = remoteVideos[id];
|
||||
|
||||
if (!remoteVideo) {
|
||||
return;
|
||||
}
|
||||
|
||||
remoteVideo.showAudioIndicator(isMuted);
|
||||
remoteVideo.updateRemoteVideoMenu();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* On video muted event.
|
||||
*/
|
||||
onVideoMute(id, value) {
|
||||
onVideoMute(id) {
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
localVideoThumbnail && localVideoThumbnail.setVideoMutedView(value);
|
||||
localVideoThumbnail && localVideoThumbnail.updateView();
|
||||
} else {
|
||||
const remoteVideo = remoteVideos[id];
|
||||
|
||||
if (remoteVideo) {
|
||||
remoteVideo.setVideoMutedView(value);
|
||||
remoteVideo.updateView();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,12 +391,6 @@ const VideoLayout = {
|
||||
const remoteVideo = remoteVideos[id];
|
||||
|
||||
if (remoteVideo) {
|
||||
// Updating only connection status indicator is not enough, because
|
||||
// when we the connection is restored while the avatar was displayed
|
||||
// (due to 'muted while disconnected' condition) we may want to show
|
||||
// the video stream again and in order to do that the display mode
|
||||
// must be updated.
|
||||
// remoteVideo.updateConnectionStatusIndicator(isActive);
|
||||
remoteVideo.updateView();
|
||||
}
|
||||
},
|
||||
@@ -494,7 +468,7 @@ const VideoLayout = {
|
||||
}
|
||||
|
||||
logger.info('Peer video type changed: ', id, newVideoType);
|
||||
remoteVideo.setScreenSharing(newVideoType === 'desktop');
|
||||
remoteVideo.updateView();
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import EventEmitter from 'events';
|
||||
import { getLogger } from 'jitsi-meet-logger';
|
||||
|
||||
import JitsiMeetJS from '../../react/features/base/lib-jitsi-meet';
|
||||
import { DISCO_REMOTE_CONTROL_FEATURE }
|
||||
from '../../service/remotecontrol/Constants';
|
||||
import * as RemoteControlEvents
|
||||
@@ -68,9 +69,7 @@ class RemoteControl extends EventEmitter {
|
||||
* @returns {void}
|
||||
*/
|
||||
init() {
|
||||
if (config.disableRemoteControl
|
||||
|| this._initialized
|
||||
|| !APP.conference.isDesktopSharingEnabled) {
|
||||
if (config.disableRemoteControl || this._initialized || !JitsiMeetJS.isDesktopSharingEnabled()) {
|
||||
return;
|
||||
}
|
||||
logger.log('Initializing remote control.');
|
||||
|
||||
222
package-lock.json
generated
@@ -4833,11 +4833,6 @@
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"@webcomponents/url": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@webcomponents/url/-/url-0.7.1.tgz",
|
||||
"integrity": "sha512-9oFDpuZ+tAogjPYQPhNEX86Npzb73A4kv9DOPsSO9aWoWgoevoP6eKx+TKMwO8BJxtTpSM9nKenHQTJ56SP2Cw=="
|
||||
},
|
||||
"@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
@@ -4927,9 +4922,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"amplitude-js": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/amplitude-js/-/amplitude-js-7.1.1.tgz",
|
||||
"integrity": "sha512-grEQf0p4V/q4aIcGYdGEJ6EquBXu91R/RorsYTQvh9O6sxjpwHf5vSDICQJq7twEElBrSHoSF77GUvC9ZTBj4A==",
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/amplitude-js/-/amplitude-js-7.3.1.tgz",
|
||||
"integrity": "sha512-dsJU9MdtDDAOtKnbHrJuVBgsL5UGxD1P2B7doGdAQ1hxxT/5mFrmJTFzi1tKe+2ir3QtcRa9B0qvH8TMsGw22A==",
|
||||
"requires": {
|
||||
"@amplitude/ua-parser-js": "0.7.24",
|
||||
"blueimp-md5": "^2.10.0",
|
||||
@@ -6410,8 +6405,7 @@
|
||||
"chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
||||
},
|
||||
"chrome-trace-event": {
|
||||
"version": "1.0.2",
|
||||
@@ -8860,6 +8854,15 @@
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
|
||||
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minipass": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"fs-write-stream-atomic": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
|
||||
@@ -8931,12 +8934,6 @@
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
"version": "1.1.1",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==",
|
||||
"optional": true
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"resolved": false,
|
||||
@@ -8988,15 +8985,6 @@
|
||||
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
|
||||
"optional": true
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "1.2.5",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minipass": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": false,
|
||||
@@ -9103,38 +9091,13 @@
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
|
||||
"optional": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.3.5",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "1.2.1",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minipass": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
@@ -9278,9 +9241,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
@@ -9380,21 +9343,6 @@
|
||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
|
||||
"optional": true
|
||||
},
|
||||
"tar": {
|
||||
"version": "4.4.8",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chownr": "^1.1.1",
|
||||
"fs-minipass": "^1.2.5",
|
||||
"minipass": "^2.3.4",
|
||||
"minizlib": "^1.1.1",
|
||||
"mkdirp": "^0.5.0",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": false,
|
||||
@@ -9415,12 +9363,6 @@
|
||||
"resolved": false,
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"optional": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.3",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -10829,8 +10771,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#4980889b24716c383311aadd760d958f6ff58277",
|
||||
"from": "github:jitsi/lib-jitsi-meet#4980889b24716c383311aadd760d958f6ff58277",
|
||||
"version": "github:jitsi/lib-jitsi-meet#6bb0b86c0a7dd22bb5798236d9b80ca578b28d21",
|
||||
"from": "github:jitsi/lib-jitsi-meet#6bb0b86c0a7dd22bb5798236d9b80ca578b28d21",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "1.0.2",
|
||||
"@jitsi/sdp-interop": "1.0.3",
|
||||
@@ -11937,6 +11879,39 @@
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
||||
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"optional": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
|
||||
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minipass": "^2.9.0"
|
||||
}
|
||||
},
|
||||
"mississippi": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
||||
@@ -12150,9 +12125,9 @@
|
||||
}
|
||||
},
|
||||
"node-forge": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz",
|
||||
"integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==",
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
|
||||
"dev": true
|
||||
},
|
||||
"node-int64": {
|
||||
@@ -12610,8 +12585,8 @@
|
||||
"dev": true
|
||||
},
|
||||
"olm": {
|
||||
"version": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
|
||||
"integrity": "sha512-kumW7B+xWMdiGSU0BrECOd+9GnhvsnnHP6qTHGPIcHTL2F0m8sYlP08hkEpN7uX/TlnHCwqpkaZXPQ0GYtVe8A=="
|
||||
"version": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
||||
"integrity": "sha512-B87bTpGIGieuV2FNauChjjQtVltwTGagQFoHm+3Dcse4amKAAGJB/I54dnP/JtbHZ+RYVoApM2OQ46Z4VH6eNg=="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
@@ -14276,15 +14251,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-native-url-polyfill": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-1.2.0.tgz",
|
||||
"integrity": "sha512-hpLZ8RyS3oGVyTOe/HjoqVoCOSkeJvrCoEB3bJsY7t9uh7kpQDV6kgvdlECEafYpxe3RzMrKLVcmWRbPU7CuAw==",
|
||||
"requires": {
|
||||
"whatwg-url-without-unicode": "8.0.0-3"
|
||||
}
|
||||
},
|
||||
"react-native-watch-connectivity": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-watch-connectivity/-/react-native-watch-connectivity-0.4.3.tgz",
|
||||
"integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
|
||||
},
|
||||
"react-native-webrtc": {
|
||||
"version": "1.84.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.84.0.tgz",
|
||||
"integrity": "sha512-xPOFbrcehuBzLnFy3keCM2HyMsyCVDQjQNAn8SIHKH/PA8Q7kZ4spuytc2E1hBTr7zH/vQ2Px+DWqu7on12jag==",
|
||||
"version": "1.84.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.84.1.tgz",
|
||||
"integrity": "sha512-ewZBgKE+YhLaivo9Wh6aiaEp8ZRvFMqblrkDl1nptQiNNH6CungoAzSOxGDnHWAxepRfiUrW5qnADrsYKmaNeQ==",
|
||||
"requires": {
|
||||
"base64-js": "^1.1.2",
|
||||
"event-target-shim": "^1.0.5",
|
||||
@@ -15030,12 +15013,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"selfsigned": {
|
||||
"version": "1.10.7",
|
||||
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz",
|
||||
"integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==",
|
||||
"version": "1.10.8",
|
||||
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz",
|
||||
"integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"node-forge": "0.9.0"
|
||||
"node-forge": "^0.10.0"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
@@ -16354,6 +16337,35 @@
|
||||
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
|
||||
"dev": true
|
||||
},
|
||||
"tar": {
|
||||
"version": "4.4.13",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
|
||||
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chownr": "^1.1.1",
|
||||
"fs-minipass": "^1.2.5",
|
||||
"minipass": "^2.8.6",
|
||||
"minizlib": "^1.2.1",
|
||||
"mkdirp": "^0.5.0",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"optional": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"temp": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz",
|
||||
@@ -17220,6 +17232,11 @@
|
||||
"defaults": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz",
|
||||
"integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA=="
|
||||
},
|
||||
"webpack": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz",
|
||||
@@ -18532,6 +18549,27 @@
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
|
||||
"integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
|
||||
},
|
||||
"whatwg-url-without-unicode": {
|
||||
"version": "8.0.0-3",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz",
|
||||
"integrity": "sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==",
|
||||
"requires": {
|
||||
"buffer": "^5.4.3",
|
||||
"punycode": "^2.1.1",
|
||||
"webidl-conversions": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"buffer": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
|
||||
"integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
|
||||
"requires": {
|
||||
"base64-js": "^1.0.2",
|
||||
"ieee754": "^1.1.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
|
||||
|
||||
12
package.json
@@ -40,8 +40,7 @@
|
||||
"@svgr/webpack": "4.3.2",
|
||||
"@tensorflow-models/body-pix": "2.0.4",
|
||||
"@tensorflow/tfjs": "1.5.1",
|
||||
"@webcomponents/url": "0.7.1",
|
||||
"amplitude-js": "7.1.1",
|
||||
"amplitude-js": "7.3.1",
|
||||
"base64-js": "1.3.1",
|
||||
"bc-css-flags": "3.0.0",
|
||||
"dropbox": "4.0.9",
|
||||
@@ -57,12 +56,12 @@
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#4980889b24716c383311aadd760d958f6ff58277",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#6bb0b86c0a7dd22bb5798236d9b80ca578b28d21",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.19",
|
||||
"moment": "2.19.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
||||
"pixelmatch": "5.1.0",
|
||||
"punycode": "2.1.1",
|
||||
"react": "16.9",
|
||||
@@ -83,8 +82,9 @@
|
||||
"react-native-splash-screen": "3.2.0",
|
||||
"react-native-svg": "10.1.0",
|
||||
"react-native-svg-transformer": "0.14.3",
|
||||
"react-native-url-polyfill": "1.2.0",
|
||||
"react-native-watch-connectivity": "0.4.3",
|
||||
"react-native-webrtc": "1.84.0",
|
||||
"react-native-webrtc": "1.84.1",
|
||||
"react-native-webview": "10.9.0",
|
||||
"react-native-youtube-iframe": "1.2.3",
|
||||
"react-redux": "7.1.0",
|
||||
@@ -92,7 +92,7 @@
|
||||
"react-transition-group": "2.4.0",
|
||||
"redux": "4.0.4",
|
||||
"redux-thunk": "2.2.0",
|
||||
"rnnoise-wasm": "github:jitsi/rnnoise-wasm.git#566a16885897704d6e6d67a1d5ac5d39781db2af",
|
||||
"rnnoise-wasm": "github:jitsi/rnnoise-wasm#566a16885897704d6e6d67a1d5ac5d39781db2af",
|
||||
"rtcstats": "github:jitsi/rtcstats#v6.2.0",
|
||||
"stackblur-canvas": "2.3.0",
|
||||
"styled-components": "3.4.9",
|
||||
|
||||
@@ -772,6 +772,22 @@ export function createTrackMutedEvent(mediaType, reason, muted = true) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event for joining a vpaas conference.
|
||||
*
|
||||
* @param {string} tenant - The conference tenant.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createVpaasConferenceJoinedEvent(tenant) {
|
||||
return {
|
||||
action: 'vpaas.conference.joined',
|
||||
attributes: {
|
||||
tenant
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event for an action on the welcome page.
|
||||
*
|
||||
|
||||
@@ -16,7 +16,7 @@ import { connect, disconnect, setLocationURL } from '../base/connection';
|
||||
import { loadConfig } from '../base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../base/media';
|
||||
import { toState } from '../base/redux';
|
||||
import { createDesiredLocalTracks, isLocalVideoTrackMuted, isLocalTrackMuted } from '../base/tracks';
|
||||
import { createDesiredLocalTracks, isLocalCameraTrackMuted, isLocalTrackMuted } from '../base/tracks';
|
||||
import {
|
||||
addHashParamsToURL,
|
||||
getBackendSafeRoomName,
|
||||
@@ -232,7 +232,7 @@ export function reloadNow() {
|
||||
function addTrackStateToURL(url, stateful) {
|
||||
const state = toState(stateful);
|
||||
const tracks = state['features/base/tracks'];
|
||||
const isVideoMuted = isLocalVideoTrackMuted(tracks);
|
||||
const isVideoMuted = isLocalCameraTrackMuted(tracks);
|
||||
const isAudioMuted = isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO);
|
||||
|
||||
return addHashParamsToURL(new URL(url), { // use new URL object in order to not pollute the passed parameter.
|
||||
@@ -298,13 +298,13 @@ export function maybeRedirectToWelcomePage(options: Object = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { isGuest, jwt } = getState()['features/base/jwt'];
|
||||
const { jwt } = getState()['features/base/jwt'];
|
||||
|
||||
let hashParam;
|
||||
|
||||
// save whether current user is guest or not, and pass auth token,
|
||||
// before navigating to close page
|
||||
window.sessionStorage.setItem('guest', isGuest);
|
||||
window.sessionStorage.setItem('guest', !jwt);
|
||||
window.sessionStorage.setItem('jwt', jwt);
|
||||
|
||||
let path = 'close.html';
|
||||
|
||||
@@ -140,18 +140,6 @@ export const P2P_STATUS_CHANGED = 'P2P_STATUS_CHANGED';
|
||||
*/
|
||||
export const SEND_TONES = 'SEND_TONES';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which sets the desktop sharing enabled flag for
|
||||
* the current conference.
|
||||
*
|
||||
* {
|
||||
* type: SET_DESKTOP_SHARING_ENABLED,
|
||||
* desktopSharingEnabled: boolean
|
||||
* }
|
||||
*/
|
||||
export const SET_DESKTOP_SHARING_ENABLED
|
||||
= 'SET_DESKTOP_SHARING_ENABLED';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which updates the current known status of the
|
||||
* Follow Me feature.
|
||||
|
||||
@@ -43,7 +43,6 @@ import {
|
||||
LOCK_STATE_CHANGED,
|
||||
P2P_STATUS_CHANGED,
|
||||
SEND_TONES,
|
||||
SET_DESKTOP_SHARING_ENABLED,
|
||||
SET_FOLLOW_ME,
|
||||
SET_PASSWORD,
|
||||
SET_PASSWORD_FAILED,
|
||||
@@ -52,7 +51,6 @@ import {
|
||||
SET_START_MUTED_POLICY
|
||||
} from './actionTypes';
|
||||
import {
|
||||
AVATAR_ID_COMMAND,
|
||||
AVATAR_URL_COMMAND,
|
||||
EMAIL_COMMAND,
|
||||
JITSI_CONFERENCE_URL_KEY
|
||||
@@ -198,13 +196,6 @@ function _addConferenceListeners(conference, dispatch) {
|
||||
botType
|
||||
})));
|
||||
|
||||
conference.addCommandListener(
|
||||
AVATAR_ID_COMMAND,
|
||||
(data, id) => dispatch(participantUpdated({
|
||||
conference,
|
||||
id,
|
||||
avatarID: data.value
|
||||
})));
|
||||
conference.addCommandListener(
|
||||
AVATAR_URL_COMMAND,
|
||||
(data, id) => dispatch(participantUpdated({
|
||||
@@ -581,22 +572,6 @@ export function sendTones(tones: string, duration: number, pause: number) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the flag for indicating if desktop sharing is enabled.
|
||||
*
|
||||
* @param {boolean} desktopSharingEnabled - True if desktop sharing is enabled.
|
||||
* @returns {{
|
||||
* type: SET_DESKTOP_SHARING_ENABLED,
|
||||
* desktopSharingEnabled: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setDesktopSharingEnabled(desktopSharingEnabled: boolean) {
|
||||
return {
|
||||
type: SET_DESKTOP_SHARING_ENABLED,
|
||||
desktopSharingEnabled
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the Follow Me feature.
|
||||
*
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/**
|
||||
* The command type for updating a participant's avatar ID.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const AVATAR_ID_COMMAND = 'avatar-id';
|
||||
|
||||
/**
|
||||
* The command type for updating a participant's avatar URL.
|
||||
*
|
||||
|
||||
@@ -14,7 +14,6 @@ import { toState } from '../redux';
|
||||
import { safeDecodeURIComponent } from '../util';
|
||||
|
||||
import {
|
||||
AVATAR_ID_COMMAND,
|
||||
AVATAR_URL_COMMAND,
|
||||
EMAIL_COMMAND,
|
||||
JITSI_CONFERENCE_URL_KEY
|
||||
@@ -74,6 +73,7 @@ export function commonUserJoinedHandling(
|
||||
} else {
|
||||
dispatch(participantJoined({
|
||||
botType: user.getBotType(),
|
||||
connectionStatus: user.getConnectionStatus(),
|
||||
conference,
|
||||
id,
|
||||
name: displayName,
|
||||
@@ -316,16 +316,12 @@ export function sendLocalParticipant(
|
||||
setDisplayName: Function,
|
||||
setLocalParticipantProperty: Function }) {
|
||||
const {
|
||||
avatarID,
|
||||
avatarURL,
|
||||
email,
|
||||
features,
|
||||
name
|
||||
} = getLocalParticipant(stateful);
|
||||
|
||||
avatarID && conference.sendCommand(AVATAR_ID_COMMAND, {
|
||||
value: avatarID
|
||||
});
|
||||
avatarURL && conference.sendCommand(AVATAR_URL_COMMAND, {
|
||||
value: avatarURL
|
||||
});
|
||||
|
||||
74
react/features/base/conference/middleware.native.js
Normal file
@@ -0,0 +1,74 @@
|
||||
// @flow
|
||||
|
||||
import { setPictureInPictureDisabled } from '../../mobile/picture-in-picture/functions';
|
||||
import { setAudioOnly } from '../audio-only';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { TOGGLE_SCREENSHARING } from '../tracks/actionTypes';
|
||||
import { destroyLocalDesktopTrackIfExists, replaceLocalTrack } from '../tracks/actions';
|
||||
import { getLocalVideoTrack, isLocalVideoTrackDesktop } from '../tracks/functions';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case TOGGLE_SCREENSHARING: {
|
||||
_toggleScreenSharing(store);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Toggles screen sharing.
|
||||
*
|
||||
* @private
|
||||
* @param {Store} store - The redux.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _toggleScreenSharing(store) {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
|
||||
const isSharing = isLocalVideoTrackDesktop(state);
|
||||
|
||||
if (isSharing) {
|
||||
dispatch(destroyLocalDesktopTrackIfExists());
|
||||
} else {
|
||||
_startScreenSharing(dispatch, state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates desktop track and replaces the local one.
|
||||
*
|
||||
* @private
|
||||
* @param {Dispatch} dispatch - The redux {@code dispatch} function.
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _startScreenSharing(dispatch, state) {
|
||||
setPictureInPictureDisabled(true);
|
||||
|
||||
JitsiMeetJS.createLocalTracks({ devices: [ 'desktop' ] })
|
||||
.then(tracks => {
|
||||
const track = tracks[0];
|
||||
const currentLocalTrack = getLocalVideoTrack(state['features/base/tracks']);
|
||||
const currentJitsiTrack = currentLocalTrack && currentLocalTrack.jitsiTrack;
|
||||
|
||||
dispatch(replaceLocalTrack(currentJitsiTrack, track));
|
||||
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
|
||||
if (audioOnly) {
|
||||
dispatch(setAudioOnly(false));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('ERROR creating ScreeSharing stream ', error);
|
||||
|
||||
setPictureInPictureDisabled(false);
|
||||
});
|
||||
}
|
||||
23
react/features/base/conference/middleware.web.js
Normal file
@@ -0,0 +1,23 @@
|
||||
// @flow
|
||||
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { TOGGLE_SCREENSHARING } from '../tracks/actionTypes';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
MiddlewareRegistry.register((/* store */) => next => action => {
|
||||
switch (action.type) {
|
||||
case TOGGLE_SCREENSHARING: {
|
||||
if (typeof APP === 'object') {
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
LOCK_STATE_CHANGED,
|
||||
P2P_STATUS_CHANGED,
|
||||
SET_DESKTOP_SHARING_ENABLED,
|
||||
SET_FOLLOW_ME,
|
||||
SET_PASSWORD,
|
||||
SET_PENDING_SUBJECT_CHANGE,
|
||||
@@ -76,9 +75,6 @@ ReducerRegistry.register(
|
||||
case P2P_STATUS_CHANGED:
|
||||
return _p2pStatusChanged(state, action);
|
||||
|
||||
case SET_DESKTOP_SHARING_ENABLED:
|
||||
return _setDesktopSharingEnabled(state, action);
|
||||
|
||||
case SET_FOLLOW_ME:
|
||||
return set(state, 'followMeEnabled', action.enabled);
|
||||
|
||||
@@ -343,21 +339,6 @@ function _p2pStatusChanged(state, action) {
|
||||
return set(state, 'p2p', action.p2p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action SET_DESKTOP_SHARING_ENABLED of the feature
|
||||
* base/conference.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature base/conference.
|
||||
* @param {Action} action - The Redux action SET_DESKTOP_SHARING_ENABLED to
|
||||
* reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _setDesktopSharingEnabled(state, action) {
|
||||
return set(state, 'desktopSharingEnabled', action.desktopSharingEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action SET_PASSWORD of the feature base/conference.
|
||||
*
|
||||
|
||||
@@ -85,6 +85,7 @@ export default [
|
||||
'disableInviteFunctions',
|
||||
'disableLocalVideoFlip',
|
||||
'disableNS',
|
||||
'disableProfile',
|
||||
'disableRemoteControl',
|
||||
'disableRemoteMute',
|
||||
'disableRtx',
|
||||
|
||||
@@ -59,7 +59,7 @@ export function getInviteURL(stateOrGetState: Function | Object): string {
|
||||
|
||||
if (inviteDomain) {
|
||||
const meetingId
|
||||
= state['features/base/config'].brandingRoomAlias || urlWithoutParams.pathname;
|
||||
= state['features/base/config'].brandingRoomAlias || urlWithoutParams.pathname.replace('/', '');
|
||||
|
||||
return `${inviteDomain}/${meetingId}`;
|
||||
}
|
||||
|
||||
3
react/features/base/icons/svg/calendar-plus.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.99996 2.50002C4.99996 2.03978 5.37306 1.66669 5.83329 1.66669C6.29353 1.66669 6.66663 2.03978 6.66663 2.50002V3.33335H13.3333V2.50002C13.3333 2.03978 13.7064 1.66669 14.1666 1.66669C14.6269 1.66669 15 2.03978 15 2.50002V3.33335H16.6666C17.5871 3.33335 18.3333 4.07955 18.3333 5.00002V16.6667C18.3333 17.5872 17.5871 18.3334 16.6666 18.3334H3.33329C2.41282 18.3334 1.66663 17.5872 1.66663 16.6667V5.00002C1.66663 4.07955 2.41282 3.33335 3.33329 3.33335H4.99996V2.50002ZM3.33329 16.6667V5.00002H16.6666V16.6667H3.33329ZM9.99996 6.66669C9.53972 6.66669 9.16663 7.03978 9.16663 7.50002V10H6.66662C6.20639 10 5.83329 10.3731 5.83329 10.8334C5.83329 11.2936 6.20639 11.6667 6.66662 11.6667H9.16663V14.1667C9.16663 14.6269 9.53972 15 9.99996 15C10.4602 15 10.8333 14.6269 10.8333 14.1667V11.6667H13.3333C13.7935 11.6667 14.1666 11.2936 14.1666 10.8334C14.1666 10.3731 13.7935 10 13.3333 10H10.8333V7.50002C10.8333 7.03978 10.4602 6.66669 9.99996 6.66669Z" fill="#0163FF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -11,6 +11,7 @@ export { default as IconAudioOnly } from './visibility.svg';
|
||||
export { default as IconAudioOnlyOff } from './visibility-off.svg';
|
||||
export { default as IconAudioRoute } from './volume.svg';
|
||||
export { default as IconBlurBackground } from './blur-background.svg';
|
||||
export { default as IconPlusCalendar } from './calendar-plus.svg';
|
||||
export { default as IconCamera } from './camera.svg';
|
||||
export { default as IconCameraDisabled } from './camera-disabled.svg';
|
||||
export { default as IconCancelSelection } from './cancel.svg';
|
||||
|
||||
@@ -14,3 +14,15 @@ import { parseURLParams } from '../util';
|
||||
export function parseJWTFromURLParams(url: URL = window.location) {
|
||||
return parseURLParams(url, true, 'search').jwt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user name after decoding the jwt.
|
||||
*
|
||||
* @param {Object} state - The app state.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getJwtName(state: Object) {
|
||||
const { user } = state['features/base/jwt'];
|
||||
|
||||
return user?.name || '';
|
||||
}
|
||||
|
||||
@@ -29,10 +29,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case SET_CONFIG:
|
||||
case SET_LOCATION_URL:
|
||||
// XXX The JSON Web Token (JWT) is not the only piece of state that we
|
||||
// have decided to store in the feature jwt, there is isGuest as well
|
||||
// which depends on the states of the features base/config and jwt. So
|
||||
// the JSON Web Token comes from the conference/room's URL and isGuest
|
||||
// needs a recalculation upon SET_CONFIG as well.
|
||||
// have decided to store in the feature jwt
|
||||
return _setConfigOrLocationURL(store, next, action);
|
||||
|
||||
case SET_JWT:
|
||||
@@ -128,12 +125,6 @@ function _setJWT(store, next, action) {
|
||||
|
||||
if (!Object.keys(actionPayload).length) {
|
||||
if (jwt) {
|
||||
const {
|
||||
enableUserRolesBasedOnToken
|
||||
} = store.getState()['features/base/config'];
|
||||
|
||||
action.isGuest = !enableUserRolesBasedOnToken;
|
||||
|
||||
let jwtPayload;
|
||||
|
||||
try {
|
||||
|
||||
@@ -4,24 +4,6 @@ import { equals, ReducerRegistry } from '../redux';
|
||||
|
||||
import { SET_JWT } from './actionTypes';
|
||||
|
||||
/**
|
||||
* The default/initial redux state of the feature jwt.
|
||||
*
|
||||
* @private
|
||||
* @type {{
|
||||
* isGuest: boolean
|
||||
* }}
|
||||
*/
|
||||
const DEFAULT_STATE = {
|
||||
/**
|
||||
* The indicator which determines whether the local participant is a guest
|
||||
* in the conference.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
isGuest: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Reduces redux actions which affect the JSON Web Token (JWT) stored in the
|
||||
* redux store.
|
||||
@@ -33,13 +15,12 @@ const DEFAULT_STATE = {
|
||||
*/
|
||||
ReducerRegistry.register(
|
||||
'features/base/jwt',
|
||||
(state = DEFAULT_STATE, action) => {
|
||||
(state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case SET_JWT: {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { type, ...payload } = action;
|
||||
const nextState = {
|
||||
...DEFAULT_STATE,
|
||||
...payload
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { SET_FILMSTRIP_ENABLED } from '../../filmstrip/actionTypes';
|
||||
import { SELECT_LARGE_VIDEO_PARTICIPANT } from '../../large-video/actionTypes';
|
||||
import { APP_STATE_CHANGED } from '../../mobile/background/actionTypes';
|
||||
import { SCREEN_SHARE_PARTICIPANTS_UPDATED, SET_TILE_VIEW } from '../../video-layout/actionTypes';
|
||||
import { shouldDisplayTileView } from '../../video-layout/functions';
|
||||
import { SET_AUDIO_ONLY } from '../audio-only/actionTypes';
|
||||
import { CONFERENCE_JOINED } from '../conference/actionTypes';
|
||||
import {
|
||||
@@ -17,6 +16,7 @@ import {
|
||||
getParticipantCount
|
||||
} from '../participants/functions';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { isLocalVideoTrackDesktop } from '../tracks/functions';
|
||||
|
||||
import { limitLastN } from './functions';
|
||||
import logger from './logger';
|
||||
@@ -79,14 +79,16 @@ function _updateLastN({ getState }) {
|
||||
}
|
||||
|
||||
if (typeof appState !== 'undefined' && appState !== 'active') {
|
||||
lastN = 0;
|
||||
lastN = isLocalVideoTrackDesktop(state) ? 1 : 0;
|
||||
} else if (audioOnly) {
|
||||
const { screenShares } = state['features/video-layout'];
|
||||
const tileViewEnabled = shouldDisplayTileView(state);
|
||||
const { screenShares, tileViewEnabled } = state['features/video-layout'];
|
||||
const largeVideoParticipantId = state['features/large-video'].participantId;
|
||||
const largeVideoParticipant
|
||||
= largeVideoParticipantId ? getParticipantById(state, largeVideoParticipantId) : undefined;
|
||||
|
||||
// Use tileViewEnabled state from redux here instead of determining if client should be in tile
|
||||
// view since we make an exception only for screenshare when in audio-only mode. If the user unpins
|
||||
// the screenshare, lastN will be set to 0 here. It will be set to 1 if screenshare has been auto pinned.
|
||||
if (!tileViewEnabled && largeVideoParticipant && !largeVideoParticipant.local) {
|
||||
lastN = (screenShares || []).includes(largeVideoParticipantId) ? 1 : 0;
|
||||
} else {
|
||||
|
||||
@@ -104,8 +104,10 @@ function _setErrorHandlers() {
|
||||
|
||||
// eslint-disable-next-line max-params
|
||||
window.onerror = (message, source, lineno, colno, error) => {
|
||||
JitsiMeetJS.getGlobalOnErrorHandler(
|
||||
message, source, lineno, colno, error);
|
||||
const errMsg = message || (error && error.message);
|
||||
const stack = error && error.stack;
|
||||
|
||||
JitsiMeetJS.getGlobalOnErrorHandler(errMsg, source, lineno, colno, stack);
|
||||
|
||||
if (oldOnErrorHandler) {
|
||||
oldOnErrorHandler(message, source, lineno, colno, error);
|
||||
@@ -115,8 +117,14 @@ function _setErrorHandlers() {
|
||||
const oldOnUnhandledRejection = window.onunhandledrejection;
|
||||
|
||||
window.onunhandledrejection = function(event) {
|
||||
JitsiMeetJS.getGlobalOnErrorHandler(
|
||||
null, null, null, null, event.reason);
|
||||
let message = event.reason;
|
||||
let stack = 'n/a';
|
||||
|
||||
if (event.reason instanceof Error) {
|
||||
({ message, stack } = event.reason);
|
||||
}
|
||||
|
||||
JitsiMeetJS.getGlobalOnErrorHandler(message, null, null, null, stack);
|
||||
|
||||
if (oldOnUnhandledRejection) {
|
||||
oldOnUnhandledRejection(event);
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
/* @flow */
|
||||
|
||||
import { toState } from '../redux';
|
||||
import { getPropertyValue } from '../settings';
|
||||
|
||||
import { VIDEO_MUTISM_AUTHORITY } from './constants';
|
||||
|
||||
|
||||
// XXX The configurations/preferences/settings startWithAudioMuted and startWithVideoMuted were introduced for
|
||||
// conferences/meetings. So it makes sense for these to not be considered outside of conferences/meetings
|
||||
// (e.g. WelcomePage). Later on, though, we introduced a "Video <-> Voice" toggle on the WelcomePage which utilizes
|
||||
// startAudioOnly outside of conferences/meetings so that particular configuration/preference/setting employs slightly
|
||||
// exclusive logic.
|
||||
const START_WITH_AUDIO_VIDEO_MUTED_SOURCES = {
|
||||
// We have startWithAudioMuted and startWithVideoMuted here:
|
||||
config: true,
|
||||
settings: true,
|
||||
|
||||
// XXX We've already overwritten base/config with urlParams. However,
|
||||
// settings are more important than the server-side config.
|
||||
// Consequently, we need to read from urlParams anyway:
|
||||
urlParams: true,
|
||||
|
||||
// We don't have startWithAudioMuted and startWithVideoMuted here:
|
||||
jwt: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether audio is currently muted.
|
||||
*
|
||||
@@ -47,6 +68,26 @@ function _isVideoMutedByAuthority(
|
||||
return Boolean(muted & videoMutismAuthority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the startWithAudioMuted by retrieving its values from config, URL and settings.
|
||||
*
|
||||
* @param {Object|Function} stateful - The redux state object or {@code getState} function.
|
||||
* @returns {boolean} - The computed startWithAudioMuted value that will be used.
|
||||
*/
|
||||
export function getStartWithAudioMuted(stateful: Object | Function) {
|
||||
return Boolean(getPropertyValue(stateful, 'startWithAudioMuted', START_WITH_AUDIO_VIDEO_MUTED_SOURCES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the startWithAudioMuted by retrieving its values from config, URL and settings.
|
||||
*
|
||||
* @param {Object|Function} stateful - The redux state object or {@code getState} function.
|
||||
* @returns {boolean} - The computed startWithAudioMuted value that will be used.
|
||||
*/
|
||||
export function getStartWithVideoMuted(stateful: Object | Function) {
|
||||
return Boolean(getPropertyValue(stateful, 'startWithVideoMuted', START_WITH_AUDIO_VIDEO_MUTED_SOURCES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether video is currently muted by the user authority.
|
||||
*
|
||||
|
||||
@@ -13,7 +13,7 @@ import { isRoomValid, SET_ROOM } from '../conference';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { getPropertyValue } from '../settings';
|
||||
import { setTrackMuted, TRACK_ADDED } from '../tracks';
|
||||
import { isLocalVideoTrackDesktop, setTrackMuted, TRACK_ADDED } from '../tracks';
|
||||
|
||||
import { setAudioMuted, setCameraFacingMode, setVideoMuted } from './actions';
|
||||
import {
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
MEDIA_TYPE,
|
||||
VIDEO_MUTISM_AUTHORITY
|
||||
} from './constants';
|
||||
import { getStartWithAudioMuted, getStartWithVideoMuted } from './functions';
|
||||
import logger from './logger';
|
||||
import {
|
||||
_AUDIO_INITIAL_MEDIA_STATE,
|
||||
@@ -72,13 +73,15 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _appStateChanged({ dispatch }, next, action) {
|
||||
const { appState } = action;
|
||||
const mute = appState !== 'active'; // Note that 'background' and 'inactive' are treated equal.
|
||||
function _appStateChanged({ dispatch, getState }, next, action) {
|
||||
if (navigator.product === 'ReactNative') {
|
||||
const { appState } = action;
|
||||
const mute = appState !== 'active' && !isLocalVideoTrackDesktop(getState());
|
||||
|
||||
sendAnalytics(createTrackMutedEvent('video', 'background mode', mute));
|
||||
sendAnalytics(createTrackMutedEvent('video', 'background mode', mute));
|
||||
|
||||
dispatch(setVideoMuted(mute, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
|
||||
dispatch(setVideoMuted(mute, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
|
||||
}
|
||||
|
||||
return next(action);
|
||||
}
|
||||
@@ -133,37 +136,8 @@ function _setRoom({ dispatch, getState }, next, action) {
|
||||
const state = getState();
|
||||
const { room } = action;
|
||||
const roomIsValid = isRoomValid(room);
|
||||
|
||||
// XXX The configurations/preferences/settings startWithAudioMuted,
|
||||
// startWithVideoMuted, and startAudioOnly were introduced for
|
||||
// conferences/meetings. So it makes sense for these to not be considered
|
||||
// outside of conferences/meetings (e.g. WelcomePage). Later on, though, we
|
||||
// introduced a "Video <-> Voice" toggle on the WelcomePage which utilizes
|
||||
// startAudioOnly outside of conferences/meetings so that particular
|
||||
// configuration/preference/setting employs slightly exclusive logic.
|
||||
const mutedSources = {
|
||||
// We have startWithAudioMuted and startWithVideoMuted here:
|
||||
config: true,
|
||||
settings: true,
|
||||
|
||||
// XXX We've already overwritten base/config with urlParams. However,
|
||||
// settings are more important than the server-side config.
|
||||
// Consequently, we need to read from urlParams anyway:
|
||||
urlParams: true,
|
||||
|
||||
// We don't have startWithAudioMuted and startWithVideoMuted here:
|
||||
jwt: false
|
||||
};
|
||||
const audioMuted
|
||||
= roomIsValid
|
||||
? Boolean(
|
||||
getPropertyValue(state, 'startWithAudioMuted', mutedSources))
|
||||
: _AUDIO_INITIAL_MEDIA_STATE.muted;
|
||||
const videoMuted
|
||||
= roomIsValid
|
||||
? Boolean(
|
||||
getPropertyValue(state, 'startWithVideoMuted', mutedSources))
|
||||
: _VIDEO_INITIAL_MEDIA_STATE.muted;
|
||||
const audioMuted = roomIsValid ? getStartWithAudioMuted(state) : _AUDIO_INITIAL_MEDIA_STATE.muted;
|
||||
const videoMuted = roomIsValid ? getStartWithVideoMuted(state) : _VIDEO_INITIAL_MEDIA_STATE.muted;
|
||||
|
||||
sendAnalytics(
|
||||
createStartMutedConfigurationEvent('local', audioMuted, videoMuted));
|
||||
|
||||
@@ -19,7 +19,8 @@ import {
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getNormalizedDisplayName,
|
||||
getParticipantDisplayName
|
||||
getParticipantDisplayName,
|
||||
figureOutMutedWhileDisconnectedStatus
|
||||
} from './functions';
|
||||
|
||||
/**
|
||||
@@ -216,12 +217,15 @@ export function muteRemoteParticipant(id) {
|
||||
* }}
|
||||
*/
|
||||
export function participantConnectionStatusChanged(id, connectionStatus) {
|
||||
return {
|
||||
type: PARTICIPANT_UPDATED,
|
||||
participant: {
|
||||
connectionStatus,
|
||||
id
|
||||
}
|
||||
return (dispatch, getState) => {
|
||||
return {
|
||||
type: PARTICIPANT_UPDATED,
|
||||
participant: {
|
||||
connectionStatus,
|
||||
id,
|
||||
mutedWhileDisconnected: figureOutMutedWhileDisconnectedStatus(getState(), id, connectionStatus)
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { getGravatarURL } from '@jitsi/js-utils/avatar';
|
||||
import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media';
|
||||
import { toState } from '../redux';
|
||||
import { getTrackByMediaTypeAndParticipant } from '../tracks';
|
||||
import { getTrackByMediaTypeAndParticipant, isRemoteTrackMuted } from '../tracks';
|
||||
import { createDeferred } from '../util';
|
||||
|
||||
import {
|
||||
@@ -298,12 +298,9 @@ export function isIconUrl(icon: ?string | ?Object) {
|
||||
*
|
||||
* @param {Object|Function} stateful - Object or function that can be resolved
|
||||
* to the Redux state.
|
||||
* @param {?boolean} ignoreToken - When true we ignore the token check.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalParticipantModerator(
|
||||
stateful: Object | Function,
|
||||
ignoreToken: ?boolean = false) {
|
||||
export function isLocalParticipantModerator(stateful: Object | Function) {
|
||||
const state = toState(stateful);
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
@@ -311,11 +308,7 @@ export function isLocalParticipantModerator(
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
localParticipant.role === PARTICIPANT_ROLE.MODERATOR
|
||||
&& (ignoreToken
|
||||
|| !state['features/base/config'].enableUserRolesBasedOnToken
|
||||
|| !state['features/base/jwt'].isGuest));
|
||||
return localParticipant.role === PARTICIPANT_ROLE.MODERATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -366,6 +359,45 @@ export function shouldRenderParticipantVideo(stateful: Object | Function, id: st
|
||||
return participantIsInLargeVideoWithScreen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Figures out the value of mutedWhileDisconnected status by taking into
|
||||
* account remote participant's network connectivity and video muted status.
|
||||
* The flag is set to <tt>true</tt> if remote participant's video gets muted
|
||||
* during his media connection disruption. This is to prevent black video
|
||||
* being render on the thumbnail, because even though once the video has
|
||||
* been played the image usually remains on the video element it seems that
|
||||
* after longer period of the video element being hidden this image can be
|
||||
* lost.
|
||||
*
|
||||
* @param {Object|Function} stateful - Object or function that can be resolved
|
||||
* to the Redux state.
|
||||
* @param {string} participantID - The ID of the participant.
|
||||
* @param {string} [connectionStatus] - A connection status to be used.
|
||||
* @returns {boolean} - The mutedWhileDisconnected value.
|
||||
*/
|
||||
export function figureOutMutedWhileDisconnectedStatus(
|
||||
stateful: Function | Object, participantID: string, connectionStatus: ?string) {
|
||||
const state = toState(stateful);
|
||||
const participant = getParticipantById(state, participantID);
|
||||
|
||||
if (!participant || participant.local) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isActive = (connectionStatus || participant.connectionStatus) === JitsiParticipantConnectionStatus.ACTIVE;
|
||||
const isVideoMuted = isRemoteTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO, participantID);
|
||||
let mutedWhileDisconnected = participant.mutedWhileDisconnected || false;
|
||||
|
||||
if (!isActive && isVideoMuted) {
|
||||
mutedWhileDisconnected = true;
|
||||
} else if (isActive && !isVideoMuted) {
|
||||
mutedWhileDisconnected = false;
|
||||
}
|
||||
|
||||
return mutedWhileDisconnected;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolves the first loadable avatar URL for a participant.
|
||||
*
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { JitsiConferenceEvents } from '../lib-jitsi-meet';
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
|
||||
import { playSound, registerSound, unregisterSound } from '../sounds';
|
||||
import { getTrackByJitsiTrack, TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from '../tracks';
|
||||
|
||||
import {
|
||||
DOMINANT_SPEAKER_CHANGED,
|
||||
@@ -41,7 +42,8 @@ import {
|
||||
getLocalParticipant,
|
||||
getParticipantById,
|
||||
getParticipantCount,
|
||||
getParticipantDisplayName
|
||||
getParticipantDisplayName,
|
||||
figureOutMutedWhileDisconnectedStatus
|
||||
} from './functions';
|
||||
import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds';
|
||||
|
||||
@@ -134,6 +136,11 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
case PARTICIPANT_UPDATED:
|
||||
return _participantJoinedOrUpdated(store, next, action);
|
||||
|
||||
case TRACK_ADDED:
|
||||
case TRACK_REMOVED:
|
||||
case TRACK_UPDATED:
|
||||
return _trackChanged(store, next, action);
|
||||
}
|
||||
|
||||
return next(action);
|
||||
@@ -283,7 +290,6 @@ function _localParticipantJoined({ getState, dispatch }, next, action) {
|
||||
const settings = getState()['features/base/settings'];
|
||||
|
||||
dispatch(localParticipantJoined({
|
||||
avatarID: settings.avatarID,
|
||||
avatarURL: settings.avatarURL,
|
||||
email: settings.email,
|
||||
name: settings.displayName
|
||||
@@ -453,6 +459,55 @@ function _registerSounds({ dispatch }) {
|
||||
dispatch(registerSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_FILE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the feature base/participants that the action there has been a change in the tracks of the participants.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action} is being dispatched.
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the specified {@code action} in the
|
||||
* specified {@code store}.
|
||||
* @param {Action} action - The redux action {@code PARTICIPANT_JOINED} or {@code PARTICIPANT_UPDATED} which is being
|
||||
* dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _trackChanged({ dispatch, getState }, next, action) {
|
||||
const { jitsiTrack } = action.track;
|
||||
let track;
|
||||
|
||||
if (action.type === TRACK_REMOVED) {
|
||||
track = getTrackByJitsiTrack(getState()['features/base/tracks'], jitsiTrack);
|
||||
}
|
||||
|
||||
const result = next(action);
|
||||
|
||||
if (action.type !== TRACK_REMOVED) {
|
||||
track = getTrackByJitsiTrack(getState()['features/base/tracks'], jitsiTrack);
|
||||
}
|
||||
|
||||
if (typeof track === 'undefined' || track.local) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const { participantId } = track;
|
||||
const state = getState();
|
||||
const participant = getParticipantById(state, participantId);
|
||||
|
||||
if (!participant) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const mutedWhileDisconnected = figureOutMutedWhileDisconnectedStatus(state, participantId);
|
||||
|
||||
if (participant.mutedWhileDisconnected !== mutedWhileDisconnected) {
|
||||
dispatch(participantUpdated({
|
||||
id: participantId,
|
||||
mutedWhileDisconnected
|
||||
}));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters sounds related with the participants feature.
|
||||
*
|
||||
|
||||
@@ -15,10 +15,16 @@ export function preloadImage(src: string | Object): Promise<string> {
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const image = document.createElement('img');
|
||||
|
||||
image.onload = () => resolve(src);
|
||||
image.onerror = reject;
|
||||
image.src = src;
|
||||
fetch(src, { referrer: '' })
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
resolve(src);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -181,7 +181,6 @@ function _participant(state: Object = {}, action) {
|
||||
*/
|
||||
function _participantJoined({ participant }) {
|
||||
const {
|
||||
avatarID,
|
||||
avatarURL,
|
||||
botType,
|
||||
connectionStatus,
|
||||
@@ -211,7 +210,6 @@ function _participantJoined({ participant }) {
|
||||
}
|
||||
|
||||
return {
|
||||
avatarID,
|
||||
avatarURL,
|
||||
botType,
|
||||
conference,
|
||||
@@ -223,6 +221,7 @@ function _participantJoined({ participant }) {
|
||||
isJigasi,
|
||||
loadableAvatarUrl,
|
||||
local: local || false,
|
||||
mutedWhileDisconnected: local ? undefined : false,
|
||||
name,
|
||||
pinned: pinned || false,
|
||||
presence,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
getLocalizedDateFormatter,
|
||||
getLocalizedDurationFormatter
|
||||
} from '../../../i18n';
|
||||
import { Icon, IconTrash } from '../../../icons';
|
||||
|
||||
import Container from './Container';
|
||||
import Text from './Text';
|
||||
@@ -38,9 +39,9 @@ type Props = {
|
||||
meetings: Array<Object>,
|
||||
|
||||
/**
|
||||
* Defines what happens when an item in the section list is clicked
|
||||
* Handler for deleting an item.
|
||||
*/
|
||||
onItemClick: Function
|
||||
onItemDelete?: Function
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -138,6 +139,25 @@ export default class MeetingsList extends Component<Props> {
|
||||
return null;
|
||||
}
|
||||
|
||||
_onDelete: Object => Function;
|
||||
|
||||
/**
|
||||
* Returns a function that is used on the onDelete callback.
|
||||
*
|
||||
* @param {Object} item - The item to be deleted.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onDelete(item) {
|
||||
const { onItemDelete } = this.props;
|
||||
|
||||
return evt => {
|
||||
evt.stopPropagation();
|
||||
|
||||
onItemDelete && onItemDelete(item);
|
||||
};
|
||||
}
|
||||
|
||||
_renderItem: (Object, number) => React$Node;
|
||||
|
||||
/**
|
||||
@@ -156,7 +176,7 @@ export default class MeetingsList extends Component<Props> {
|
||||
title,
|
||||
url
|
||||
} = meeting;
|
||||
const { hideURL = false } = this.props;
|
||||
const { hideURL = false, onItemDelete } = this.props;
|
||||
const onPress = this._onPress(url);
|
||||
const rootClassName
|
||||
= `item ${
|
||||
@@ -168,10 +188,10 @@ export default class MeetingsList extends Component<Props> {
|
||||
key = { index }
|
||||
onClick = { onPress }>
|
||||
<Container className = 'left-column'>
|
||||
<Text className = 'date'>
|
||||
<Text className = 'title'>
|
||||
{ _toDateString(date) }
|
||||
</Text>
|
||||
<Text>
|
||||
<Text className = 'subtitle'>
|
||||
{ _toTimeString(time) }
|
||||
</Text>
|
||||
</Container>
|
||||
@@ -187,13 +207,18 @@ export default class MeetingsList extends Component<Props> {
|
||||
}
|
||||
{
|
||||
typeof duration === 'number' ? (
|
||||
<Text>
|
||||
<Text className = 'subtitle'>
|
||||
{ getLocalizedDurationFormatter(duration) }
|
||||
</Text>) : null
|
||||
}
|
||||
</Container>
|
||||
<Container className = 'actions'>
|
||||
{ elementAfter || null }
|
||||
|
||||
{ onItemDelete && <Icon
|
||||
className = 'delete-meeting'
|
||||
onClick = { this._onDelete(meeting) }
|
||||
src = { IconTrash } />}
|
||||
</Container>
|
||||
</Container>
|
||||
);
|
||||
|
||||
@@ -225,7 +225,6 @@ class Watermarks extends Component<Props, State> {
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
const { isGuest } = state['features/base/jwt'];
|
||||
const {
|
||||
customizationReady,
|
||||
customizationFailed,
|
||||
@@ -239,12 +238,11 @@ function _mapStateToProps(state, ownProps) {
|
||||
DEFAULT_LOGO_URL,
|
||||
JITSI_WATERMARK_LINK,
|
||||
SHOW_JITSI_WATERMARK,
|
||||
SHOW_JITSI_WATERMARK_FOR_GUESTS,
|
||||
filmStripOnly
|
||||
} = interfaceConfig;
|
||||
let _showJitsiWatermark = (!filmStripOnly
|
||||
&& (customizationReady && !customizationFailed)
|
||||
&& (SHOW_JITSI_WATERMARK || (isGuest && SHOW_JITSI_WATERMARK_FOR_GUESTS)))
|
||||
&& SHOW_JITSI_WATERMARK)
|
||||
|| !isValidRoom;
|
||||
let _logoUrl = logoImageUrl;
|
||||
let _logoLink = logoClickUrl;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* type: SETTINGS_UPDATED,
|
||||
* settings: {
|
||||
* audioOutputDeviceId: string,
|
||||
* avatarID: string,
|
||||
* avatarURL: string,
|
||||
* cameraDeviceId: string,
|
||||
* displayName: string,
|
||||
|
||||
@@ -8,7 +8,6 @@ import { SETTINGS_UPDATED } from './actionTypes';
|
||||
* type: SETTINGS_UPDATED,
|
||||
* settings: {
|
||||
* audioOutputDeviceId: string,
|
||||
* avatarID: string,
|
||||
* avatarURL: string,
|
||||
* cameraDeviceId: string,
|
||||
* displayName: string,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// @flow
|
||||
import _ from 'lodash';
|
||||
|
||||
import { PREJOIN_INITIALIZED } from '../../prejoin/actionTypes';
|
||||
import { APP_WILL_MOUNT } from '../app';
|
||||
import { setAudioOnly } from '../audio-only';
|
||||
import { SET_LOCATION_URL } from '../connection/actionTypes'; // minimize imports to avoid circular imports
|
||||
import { getJwtName } from '../jwt/functions';
|
||||
import { getLocalParticipant, participantUpdated } from '../participants';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { parseURLParams } from '../util';
|
||||
@@ -27,6 +29,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case APP_WILL_MOUNT:
|
||||
_initializeCallIntegration(store);
|
||||
break;
|
||||
case PREJOIN_INITIALIZED: {
|
||||
_maybeUpdateDisplayName(store);
|
||||
break;
|
||||
}
|
||||
case SETTINGS_UPDATED:
|
||||
_maybeHandleCallIntegrationChange(action);
|
||||
_maybeSetAudioOnly(store, action);
|
||||
@@ -115,6 +121,26 @@ function _maybeSetAudioOnly(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the display name to the one in JWT if there is one.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _maybeUpdateDisplayName({ dispatch, getState }) {
|
||||
const state = getState();
|
||||
const hasJwt = Boolean(state['features/base/jwt'].jwt);
|
||||
|
||||
if (hasJwt) {
|
||||
const displayName = getJwtName(state);
|
||||
|
||||
dispatch(updateSettings({
|
||||
displayName
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the local participant according to settings changes.
|
||||
*
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import { randomHexString } from '@jitsi/js-utils/random';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { APP_WILL_MOUNT } from '../app/actionTypes';
|
||||
@@ -19,7 +18,6 @@ import logger from './logger';
|
||||
*/
|
||||
const DEFAULT_STATE = {
|
||||
audioOutputDeviceId: undefined,
|
||||
avatarID: undefined,
|
||||
avatarURL: undefined,
|
||||
cameraDeviceId: undefined,
|
||||
disableCallIntegration: undefined,
|
||||
@@ -126,24 +124,16 @@ function _initSettings(featureState) {
|
||||
// jibri, and remove the old settings.js values.
|
||||
const savedDisplayName = jitsiLocalStorage.getItem('displayname');
|
||||
const savedEmail = jitsiLocalStorage.getItem('email');
|
||||
let avatarID = _.escape(jitsiLocalStorage.getItem('avatarId'));
|
||||
|
||||
// The helper _.escape will convert null to an empty strings. The empty
|
||||
// string will be saved in settings. On app re-load, because an empty string
|
||||
// is a defined value, it will override any value found in local storage.
|
||||
// The workaround is sidestepping _.escape when the value is not set in
|
||||
// local storage.
|
||||
const displayName
|
||||
= savedDisplayName === null ? undefined : _.escape(savedDisplayName);
|
||||
const displayName = savedDisplayName === null ? undefined : _.escape(savedDisplayName);
|
||||
const email = savedEmail === null ? undefined : _.escape(savedEmail);
|
||||
|
||||
if (!avatarID) {
|
||||
// if there is no avatar id, we generate a unique one and use it forever
|
||||
avatarID = randomHexString(32);
|
||||
}
|
||||
|
||||
settings = assignIfDefined({
|
||||
avatarID,
|
||||
displayName,
|
||||
email
|
||||
}, settings);
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
MEDIA_TYPE,
|
||||
setAudioMuted,
|
||||
setVideoMuted,
|
||||
VIDEO_MUTISM_AUTHORITY
|
||||
VIDEO_MUTISM_AUTHORITY,
|
||||
VIDEO_TYPE
|
||||
} from '../media';
|
||||
import { getLocalParticipant } from '../participants';
|
||||
|
||||
@@ -24,7 +25,13 @@ import {
|
||||
TRACK_UPDATED,
|
||||
TRACK_WILL_CREATE
|
||||
} from './actionTypes';
|
||||
import { createLocalTracksF, getLocalTrack, getLocalTracks, getTrackByJitsiTrack } from './functions';
|
||||
import {
|
||||
createLocalTracksF,
|
||||
getLocalTrack,
|
||||
getLocalTracks,
|
||||
getLocalVideoTrack,
|
||||
getTrackByJitsiTrack
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
@@ -40,6 +47,8 @@ export function createDesiredLocalTracks(...desiredTypes) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
||||
dispatch(destroyLocalDesktopTrackIfExists());
|
||||
|
||||
if (desiredTypes.length === 0) {
|
||||
const { audio, video } = state['features/base/media'];
|
||||
|
||||
@@ -663,6 +672,22 @@ function _trackCreateCanceled(mediaType) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* If thee local track if of type Desktop, it calls _disposeAndRemoveTracks) on it.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function destroyLocalDesktopTrackIfExists() {
|
||||
return (dispatch, getState) => {
|
||||
const videoTrack = getLocalVideoTrack(getState()['features/base/tracks']);
|
||||
const isDesktopTrack = videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
|
||||
|
||||
if (isDesktopTrack) {
|
||||
dispatch(_disposeAndRemoveTracks([ videoTrack.jitsiTrack ]));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets UID of the displayed no data from source notification. Used to track
|
||||
* if the notification was previously displayed in this context.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* global APP */
|
||||
|
||||
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE, setAudioMuted } from '../media';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE, setAudioMuted } from '../media';
|
||||
import {
|
||||
getUserSelectedCameraDeviceId,
|
||||
getUserSelectedMicDeviceId
|
||||
@@ -346,12 +346,12 @@ export function getTracksByMediaType(tracks, mediaType) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the local video track in the given set of tracks is muted.
|
||||
* Checks if the local video camera track in the given set of tracks is muted.
|
||||
*
|
||||
* @param {Track[]} tracks - List of all tracks.
|
||||
* @returns {Track[]}
|
||||
*/
|
||||
export function isLocalVideoTrackMuted(tracks) {
|
||||
export function isLocalCameraTrackMuted(tracks) {
|
||||
const presenterTrack = getLocalTrack(tracks, MEDIA_TYPE.PRESENTER);
|
||||
const videoTrack = getLocalTrack(tracks, MEDIA_TYPE.VIDEO);
|
||||
|
||||
@@ -383,6 +383,19 @@ export function isLocalTrackMuted(tracks, mediaType) {
|
||||
return !track || track.muted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the local video track is of type DESKtOP.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalVideoTrackDesktop(state) {
|
||||
const videoTrack = getLocalVideoTrack(state['features/base/tracks']);
|
||||
|
||||
return videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the remote track of the given media type and the given
|
||||
* participant is muted, false otherwise.
|
||||
|
||||
@@ -159,7 +159,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
} else if (jitsiTrack.isLocal()) {
|
||||
APP.conference.setVideoMuteStatus(muted);
|
||||
} else {
|
||||
APP.UI.setVideoMuted(participantID, muted);
|
||||
APP.UI.setVideoMuted(participantID);
|
||||
}
|
||||
APP.UI.onPeerVideoTypeChanged(participantID, jitsiTrack.videoType);
|
||||
} else if (jitsiTrack.isLocal()) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { sendAnalytics, createVpaasConferenceJoinedEvent } from '../analytics';
|
||||
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
|
||||
import { PARTICIPANT_JOINED } from '../base/participants/actionTypes';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import { SET_BILLING_ID } from './actionTypes';
|
||||
import { countEndpoint } from './actions';
|
||||
import { setBillingId } from './functions';
|
||||
import { isVpaasMeeting, extractVpaasTenantFromPath, setBillingId } from './functions';
|
||||
|
||||
/**
|
||||
* The redux middleware for billing counter.
|
||||
@@ -14,6 +16,11 @@ import { setBillingId } from './functions';
|
||||
|
||||
MiddlewareRegistry.register(store => next => async action => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED: {
|
||||
_maybeTrackVpaasConferenceJoin(store.getState());
|
||||
|
||||
break;
|
||||
}
|
||||
case SET_BILLING_ID: {
|
||||
setBillingId(action.value);
|
||||
|
||||
@@ -34,3 +41,17 @@ MiddlewareRegistry.register(store => next => async action => {
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tracks the conference join event if the meeting is a vpaas one.
|
||||
*
|
||||
* @param {Store} state - The app state.
|
||||
* @returns {Function}
|
||||
*/
|
||||
function _maybeTrackVpaasConferenceJoin(state) {
|
||||
if (isVpaasMeeting(state)) {
|
||||
sendAnalytics(createVpaasConferenceJoinedEvent(
|
||||
extractVpaasTenantFromPath(
|
||||
state['features/base/connection'].locationURL.pathname)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Icon, IconPlusCalendar } from '../../base/icons';
|
||||
import { AbstractPage } from '../../base/react';
|
||||
import { connect } from '../../base/redux';
|
||||
import { openSettingsDialog, SETTINGS_TABS } from '../../settings';
|
||||
@@ -185,16 +186,22 @@ class CalendarList extends AbstractPage<Props> {
|
||||
|
||||
return (
|
||||
<div className = 'meetings-list-empty'>
|
||||
<p className = 'description'>
|
||||
<div className = 'meetings-list-empty-image'>
|
||||
<img src = '/images/calendar.svg' />
|
||||
</div>
|
||||
<div className = 'description'>
|
||||
{ t('welcomepage.connectCalendarText', {
|
||||
app: interfaceConfig.APP_NAME,
|
||||
provider: interfaceConfig.PROVIDER_NAME
|
||||
}) }
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className = 'button'
|
||||
className = 'meetings-list-empty-button'
|
||||
onClick = { this._onOpenSettings }>
|
||||
{ t('welcomepage.connectCalendarButton') }
|
||||
<Icon
|
||||
className = 'meetings-list-empty-icon'
|
||||
src = { IconPlusCalendar } />
|
||||
<span>{ t('welcomepage.connectCalendarButton') }</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import Tooltip from '@atlaskit/tooltip';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Icon, IconAdd } from '../../base/icons';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link JoinButton}.
|
||||
@@ -60,7 +61,9 @@ class JoinButton extends Component<Props> {
|
||||
<div
|
||||
className = 'button join-button'
|
||||
onClick = { this._onClick }>
|
||||
{ t('calendarSync.join') }
|
||||
<Icon
|
||||
size = '14'
|
||||
src = { IconAdd } />
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -152,6 +152,7 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
|
||||
_isSupportedEnvironment() {
|
||||
return interfaceConfig.SHOW_CHROME_EXTENSION_BANNER
|
||||
&& browser.isChrome()
|
||||
&& !browser.isTwa()
|
||||
&& !isMobileBrowser()
|
||||
&& !this.props.isVpaas;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconConnectionActive, IconConnectionInactive } from '../../../base/icons';
|
||||
import { JitsiParticipantConnectionStatus } from '../../../base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../../../base/media';
|
||||
import { getLocalParticipant, getParticipantById } from '../../../base/participants';
|
||||
import { Popover } from '../../../base/popover';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
|
||||
@@ -61,6 +62,12 @@ const QUALITY_TO_WIDTH: Array<Object> = [
|
||||
*/
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* The current condition of the user's connection, matching one of the
|
||||
* enumerated values in the library.
|
||||
*/
|
||||
_connectionStatus: string,
|
||||
|
||||
/**
|
||||
* Whether or not the component should ignore setting a visibility class for
|
||||
* hiding the component when the connection quality is not strong.
|
||||
@@ -72,12 +79,6 @@ type Props = AbstractProps & {
|
||||
*/
|
||||
audioSsrc: number,
|
||||
|
||||
/**
|
||||
* The current condition of the user's connection, matching one of the
|
||||
* enumerated values in the library.
|
||||
*/
|
||||
connectionStatus: string,
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
@@ -200,13 +201,13 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||
* @returns {string}
|
||||
*/
|
||||
_getConnectionColorClass() {
|
||||
const { connectionStatus } = this.props;
|
||||
const { _connectionStatus } = this.props;
|
||||
const { percent } = this.state.stats;
|
||||
const { INACTIVE, INTERRUPTED } = JitsiParticipantConnectionStatus;
|
||||
|
||||
if (connectionStatus === INACTIVE) {
|
||||
if (_connectionStatus === INACTIVE) {
|
||||
return 'status-other';
|
||||
} else if (connectionStatus === INTERRUPTED) {
|
||||
} else if (_connectionStatus === INTERRUPTED) {
|
||||
return 'status-lost';
|
||||
} else if (typeof percent === 'undefined') {
|
||||
return 'status-high';
|
||||
@@ -224,7 +225,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||
_getConnectionStatusTip() {
|
||||
let tipKey;
|
||||
|
||||
switch (this.props.connectionStatus) {
|
||||
switch (this.props._connectionStatus) {
|
||||
case JitsiParticipantConnectionStatus.INTERRUPTED:
|
||||
tipKey = 'connectionindicator.quality.lost';
|
||||
break;
|
||||
@@ -275,12 +276,12 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||
* @returns {string}
|
||||
*/
|
||||
_getVisibilityClass() {
|
||||
const { connectionStatus } = this.props;
|
||||
const { _connectionStatus } = this.props;
|
||||
|
||||
return this.state.showIndicator
|
||||
|| this.props.alwaysVisible
|
||||
|| connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED
|
||||
|| connectionStatus === JitsiParticipantConnectionStatus.INACTIVE
|
||||
|| _connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED
|
||||
|| _connectionStatus === JitsiParticipantConnectionStatus.INACTIVE
|
||||
? 'show-connection-indicator' : 'hide-connection-indicator';
|
||||
}
|
||||
|
||||
@@ -304,7 +305,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderIcon() {
|
||||
if (this.props.connectionStatus
|
||||
if (this.props._connectionStatus
|
||||
=== JitsiParticipantConnectionStatus.INACTIVE) {
|
||||
return (
|
||||
<span className = 'connection_ninja'>
|
||||
@@ -319,7 +320,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||
let iconWidth;
|
||||
let emptyIconWrapperClassName = 'connection_empty';
|
||||
|
||||
if (this.props.connectionStatus
|
||||
if (this.props._connectionStatus
|
||||
=== JitsiParticipantConnectionStatus.INTERRUPTED) {
|
||||
|
||||
// emptyIconWrapperClassName is used by the torture tests to
|
||||
@@ -434,21 +435,29 @@ export function _mapDispatchToProps(dispatch: Dispatch<any>) {
|
||||
* @returns {Props}
|
||||
*/
|
||||
export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
|
||||
const { participantId } = ownProps;
|
||||
const conference = state['features/base/conference'].conference;
|
||||
const participant
|
||||
= typeof participantId === 'undefined' ? getLocalParticipant(state) : getParticipantById(state, participantId);
|
||||
const props = {
|
||||
_connectionStatus: participant?.connectionStatus
|
||||
};
|
||||
|
||||
if (conference) {
|
||||
const firstVideoTrack = getTrackByMediaTypeAndParticipant(
|
||||
state['features/base/tracks'], MEDIA_TYPE.VIDEO, ownProps.participantId);
|
||||
state['features/base/tracks'], MEDIA_TYPE.VIDEO, participantId);
|
||||
const firstAudioTrack = getTrackByMediaTypeAndParticipant(
|
||||
state['features/base/tracks'], MEDIA_TYPE.AUDIO, ownProps.participantId);
|
||||
state['features/base/tracks'], MEDIA_TYPE.AUDIO, participantId);
|
||||
|
||||
return {
|
||||
...props,
|
||||
audioSsrc: firstAudioTrack ? conference.getSsrcByTrack(firstAudioTrack.jitsiTrack) : undefined,
|
||||
videoSsrc: firstVideoTrack ? conference.getSsrcByTrack(firstVideoTrack.jitsiTrack) : undefined
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
return {
|
||||
...props
|
||||
};
|
||||
}
|
||||
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(ConnectionIndicator));
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { MEDIA_TYPE } from '../../../base/media';
|
||||
import { getLocalParticipant, getParticipantById, PARTICIPANT_ROLE } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { getTrackByMediaTypeAndParticipant, isLocalTrackMuted, isRemoteTrackMuted } from '../../../base/tracks';
|
||||
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
|
||||
|
||||
import AudioMutedIndicator from './AudioMutedIndicator';
|
||||
@@ -23,25 +25,25 @@ type Props = {
|
||||
*/
|
||||
_currentLayout: string,
|
||||
|
||||
/**
|
||||
* Indicates if the audio muted indicator should be visible or not.
|
||||
*/
|
||||
_showAudioMutedIndicator: Boolean,
|
||||
|
||||
/**
|
||||
* Indicates if the moderator indicator should be visible or not.
|
||||
*/
|
||||
_showModeratorIndicator: Boolean,
|
||||
|
||||
/**
|
||||
* Indicates if the audio muted indicator should be visible or not.
|
||||
*/
|
||||
showAudioMutedIndicator: Boolean,
|
||||
|
||||
/**
|
||||
* Indicates if the screen share indicator should be visible or not.
|
||||
*/
|
||||
showScreenShareIndicator: Boolean,
|
||||
_showScreenShareIndicator: Boolean,
|
||||
|
||||
/**
|
||||
* Indicates if the video muted indicator should be visible or not.
|
||||
*/
|
||||
showVideoMutedIndicator: Boolean,
|
||||
_showVideoMutedIndicator: Boolean,
|
||||
|
||||
/**
|
||||
* The ID of the participant for which the status bar is rendered.
|
||||
@@ -64,10 +66,10 @@ class StatusIndicators extends Component<Props> {
|
||||
render() {
|
||||
const {
|
||||
_currentLayout,
|
||||
_showAudioMutedIndicator,
|
||||
_showModeratorIndicator,
|
||||
showAudioMutedIndicator,
|
||||
showScreenShareIndicator,
|
||||
showVideoMutedIndicator
|
||||
_showScreenShareIndicator,
|
||||
_showVideoMutedIndicator
|
||||
} = this.props;
|
||||
let tooltipPosition;
|
||||
|
||||
@@ -84,9 +86,9 @@ class StatusIndicators extends Component<Props> {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ showAudioMutedIndicator ? <AudioMutedIndicator tooltipPosition = { tooltipPosition } /> : null }
|
||||
{ showScreenShareIndicator ? <ScreenShareIndicator tooltipPosition = { tooltipPosition } /> : null }
|
||||
{ showVideoMutedIndicator ? <VideoMutedIndicator tooltipPosition = { tooltipPosition } /> : null }
|
||||
{ _showAudioMutedIndicator ? <AudioMutedIndicator tooltipPosition = { tooltipPosition } /> : null }
|
||||
{ _showScreenShareIndicator ? <ScreenShareIndicator tooltipPosition = { tooltipPosition } /> : null }
|
||||
{ _showVideoMutedIndicator ? <VideoMutedIndicator tooltipPosition = { tooltipPosition } /> : null }
|
||||
{ _showModeratorIndicator ? <ModeratorIndicator tooltipPosition = { tooltipPosition } /> : null }
|
||||
</div>
|
||||
);
|
||||
@@ -101,7 +103,8 @@ class StatusIndicators extends Component<Props> {
|
||||
* @private
|
||||
* @returns {{
|
||||
* _currentLayout: string,
|
||||
* _showModeratorIndicator: boolean
|
||||
* _showModeratorIndicator: boolean,
|
||||
* _showVideoMutedIndicator: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
@@ -110,10 +113,29 @@ function _mapStateToProps(state, ownProps) {
|
||||
// Only the local participant won't have id for the time when the conference is not yet joined.
|
||||
const participant = participantID ? getParticipantById(state, participantID) : getLocalParticipant(state);
|
||||
|
||||
const tracks = state['features/base/tracks'];
|
||||
let isVideoMuted = true;
|
||||
let isAudioMuted = true;
|
||||
let isScreenSharing = false;
|
||||
|
||||
if (participant?.local) {
|
||||
isVideoMuted = isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO);
|
||||
isAudioMuted = isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO);
|
||||
} else if (!participant?.isFakeParticipant) { // remote participants excluding shared video
|
||||
const track = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantID);
|
||||
|
||||
isScreenSharing = track?.videoType === 'desktop';
|
||||
isVideoMuted = isRemoteTrackMuted(tracks, MEDIA_TYPE.VIDEO, participantID);
|
||||
isAudioMuted = isRemoteTrackMuted(tracks, MEDIA_TYPE.AUDIO, participantID);
|
||||
}
|
||||
|
||||
return {
|
||||
_currentLayout: getCurrentLayout(state),
|
||||
_showAudioMutedIndicator: isAudioMuted,
|
||||
_showModeratorIndicator:
|
||||
!interfaceConfig.DISABLE_FOCUS_INDICATOR && participant && participant.role === PARTICIPANT_ROLE.MODERATOR
|
||||
!interfaceConfig.DISABLE_FOCUS_INDICATOR && participant && participant.role === PARTICIPANT_ROLE.MODERATOR,
|
||||
_showScreenShareIndicator: isScreenSharing,
|
||||
_showVideoMutedIndicator: isVideoMuted
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -352,7 +352,7 @@ export function invitePeopleAndChatRooms( // eslint-disable-line max-params
|
||||
export function isAddPeopleEnabled(state: Object): boolean {
|
||||
const { peopleSearchUrl } = state['features/base/config'];
|
||||
|
||||
return !isGuest(state) && Boolean(peopleSearchUrl);
|
||||
return state['features/base/jwt'].jwt && Boolean(peopleSearchUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,16 +368,6 @@ export function isDialOutEnabled(state: Object): boolean {
|
||||
&& conference && conference.isSIPCallingSupported();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user is guest or not.
|
||||
*
|
||||
* @param {Object} state - Current state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isGuest(state: Object): boolean {
|
||||
return state['features/base/jwt'].isGuest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a string looks like it could be for a phone number.
|
||||
*
|
||||
|
||||
@@ -17,20 +17,30 @@ export function captureLargeVideoScreenshot() {
|
||||
return (dispatch: Dispatch<any>, getState: Function): Promise<string> => {
|
||||
const state = getState();
|
||||
const largeVideo = state['features/large-video'];
|
||||
const promise = Promise.resolve();
|
||||
|
||||
if (!largeVideo) {
|
||||
return Promise.resolve();
|
||||
return promise;
|
||||
}
|
||||
const tracks = state['features/base/tracks'];
|
||||
const { jitsiTrack } = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, largeVideo.participantId);
|
||||
const videoStream = jitsiTrack.getOriginalStream();
|
||||
const participantTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, largeVideo.participantId);
|
||||
|
||||
// Participants that join the call video muted do not have a jitsiTrack attached.
|
||||
if (!(participantTrack && participantTrack.jitsiTrack)) {
|
||||
return promise;
|
||||
}
|
||||
const videoStream = participantTrack.jitsiTrack.getOriginalStream();
|
||||
|
||||
if (!videoStream) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Get the video element for the large video, cast HTMLElement to HTMLVideoElement to make flow happy.
|
||||
/* eslint-disable-next-line no-extra-parens*/
|
||||
const videoElement = ((document.getElementById('largeVideo'): any): HTMLVideoElement);
|
||||
|
||||
if (!videoElement) {
|
||||
return Promise.resolve();
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Create a HTML canvas and draw video on to the canvas.
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../base/color-scheme';
|
||||
import { ParticipantView } from '../../base/participants';
|
||||
import { ParticipantView, getParticipantById } from '../../base/participants';
|
||||
import { connect } from '../../base/redux';
|
||||
import { StyleType } from '../../base/styles';
|
||||
import { isLocalVideoTrackDesktop } from '../../base/tracks/functions';
|
||||
|
||||
import { AVATAR_SIZE } from './styles';
|
||||
|
||||
@@ -14,6 +15,11 @@ import { AVATAR_SIZE } from './styles';
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Whether video should be disabled.
|
||||
*/
|
||||
_disableVideo: boolean,
|
||||
|
||||
/**
|
||||
* Application's viewport height.
|
||||
*/
|
||||
@@ -112,6 +118,7 @@ class LargeVideo extends PureComponent<Props, State> {
|
||||
useConnectivityInfoLabel
|
||||
} = this.state;
|
||||
const {
|
||||
_disableVideo,
|
||||
_participantId,
|
||||
_styles,
|
||||
onClick
|
||||
@@ -120,6 +127,7 @@ class LargeVideo extends PureComponent<Props, State> {
|
||||
return (
|
||||
<ParticipantView
|
||||
avatarSize = { avatarSize }
|
||||
disableVideo = { _disableVideo }
|
||||
onPress = { onClick }
|
||||
participantId = { _participantId }
|
||||
style = { _styles.largeVideo }
|
||||
@@ -139,11 +147,19 @@ class LargeVideo extends PureComponent<Props, State> {
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { participantId } = state['features/large-video'];
|
||||
const participant = getParticipantById(state, participantId);
|
||||
const { clientHeight: height, clientWidth: width } = state['features/base/responsive-ui'];
|
||||
let disableVideo = false;
|
||||
|
||||
if (participant?.local) {
|
||||
disableVideo = isLocalVideoTrackDesktop(state);
|
||||
}
|
||||
|
||||
return {
|
||||
_disableVideo: disableVideo,
|
||||
_height: height,
|
||||
_participantId: state['features/large-video'].participantId,
|
||||
_participantId: participantId,
|
||||
_styles: ColorSchemeRegistry.get(state, 'LargeVideo'),
|
||||
_width: width
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ import { translate } from '../../../base/i18n';
|
||||
import { IconMenuDown } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
import { isLocalVideoTrackDesktop } from '../../../base/tracks/functions';
|
||||
import { enterPictureInPicture } from '../actions';
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
@@ -63,7 +64,7 @@ class PictureInPictureButton extends AbstractButton<Props, *> {
|
||||
*/
|
||||
function _mapStateToProps(state): Object {
|
||||
const flag = Boolean(getFeatureFlag(state, PIP_ENABLED));
|
||||
let enabled = flag;
|
||||
let enabled = flag && !isLocalVideoTrackDesktop(state);
|
||||
|
||||
// Override flag for Android, since it might be unsupported.
|
||||
if (Platform.OS === 'android' && !NativeModules.PictureInPicture.SUPPORTED) {
|
||||
|
||||
15
react/features/mobile/picture-in-picture/functions.js
Normal file
@@ -0,0 +1,15 @@
|
||||
// @flow
|
||||
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
/**
|
||||
* Enabled/Disables the PictureInPicture mode in PiP native module.
|
||||
*
|
||||
* @param {boolean} disabled - Whether the PiP mode should be disabled.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function setPictureInPictureDisabled(disabled: boolean) {
|
||||
const { PictureInPicture } = NativeModules;
|
||||
|
||||
PictureInPicture.setPictureInPictureDisabled(disabled);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
export * from './functions';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Platform } from 'react-native';
|
||||
import BackgroundTimer from 'react-native-background-timer';
|
||||
|
||||
import '@webcomponents/url'; // Polyfill for URL constructor
|
||||
import 'react-native-url-polyfill/auto'; // Complete URL polyfill.
|
||||
|
||||
import Storage from './Storage';
|
||||
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
*/
|
||||
export const PREJOIN_START_CONFERENCE = 'PREJOIN_START_CONFERENCE';
|
||||
|
||||
/**
|
||||
* Action type to signal that prejoin page was initialized.
|
||||
*/
|
||||
export const PREJOIN_INITIALIZED = 'PREJOIN_INITIALIZED';
|
||||
|
||||
/**
|
||||
* Action type to set the status of the device.
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,7 @@ import { executeDialOutRequest, executeDialOutStatusRequest, getDialInfoPageURL
|
||||
import { showErrorNotification } from '../notifications';
|
||||
|
||||
import {
|
||||
PREJOIN_INITIALIZED,
|
||||
PREJOIN_START_CONFERENCE,
|
||||
SET_DEVICE_STATUS,
|
||||
SET_DIALOUT_COUNTRY,
|
||||
@@ -195,7 +196,7 @@ export function dialOut(onSuccess: Function, onFail: Function) {
|
||||
export function initPrejoin(tracks: Object[], errors: Object) {
|
||||
return async function(dispatch: Function) {
|
||||
dispatch(setPrejoinDeviceErrors(errors));
|
||||
|
||||
dispatch(prejoinInitialized());
|
||||
|
||||
tracks.forEach(track => dispatch(trackAdded(track)));
|
||||
};
|
||||
@@ -269,6 +270,17 @@ export function openDialInPage() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to signal that the prejoin page has been initialized.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
function prejoinInitialized() {
|
||||
return {
|
||||
type: PREJOIN_INITIALIZED
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new audio track based on a device id and replaces the current one.
|
||||
*
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { Dispatch } from 'redux';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { MeetingsList } from '../../base/react';
|
||||
import { connect } from '../../base/redux';
|
||||
import { deleteRecentListEntry } from '../actions';
|
||||
import { isRecentListEnabled, toDisplayableList } from '../functions';
|
||||
|
||||
import AbstractRecentList from './AbstractRecentList';
|
||||
@@ -55,6 +56,19 @@ class RecentList extends AbstractRecentList<Props> {
|
||||
this._getRenderListEmptyComponent
|
||||
= this._getRenderListEmptyComponent.bind(this);
|
||||
this._onPress = this._onPress.bind(this);
|
||||
this._onItemDelete = this._onItemDelete.bind(this);
|
||||
}
|
||||
|
||||
_onItemDelete: Object => void;
|
||||
|
||||
/**
|
||||
* Deletes a recent entry.
|
||||
*
|
||||
* @param {Object} entry - The entry to be deleted.
|
||||
* @inheritdoc
|
||||
*/
|
||||
_onItemDelete(entry) {
|
||||
this.props.dispatch(deleteRecentListEntry(entry));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,6 +92,7 @@ class RecentList extends AbstractRecentList<Props> {
|
||||
hideURL = { true }
|
||||
listEmptyComponent = { this._getRenderListEmptyComponent() }
|
||||
meetings = { recentList }
|
||||
onItemDelete = { this._onItemDelete }
|
||||
onPress = { this._onPress } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { parseURIString, safeDecodeURIComponent } from '../base/util';
|
||||
*/
|
||||
export function toDisplayableList(recentList) {
|
||||
return (
|
||||
recentList.slice(-3).reverse()
|
||||
recentList.reverse()
|
||||
.map(item => {
|
||||
return {
|
||||
date: item.date,
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
import { openDialog } from '../../../base/dialog';
|
||||
import { IconLiveStreaming } from '../../../base/icons';
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import { getLocalParticipant } from '../../../base/participants';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
isLocalParticipantModerator
|
||||
} from '../../../base/participants';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
import { getActiveSession } from '../../functions';
|
||||
|
||||
@@ -125,13 +128,14 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
// If the containing component provides the visible prop, that is one
|
||||
// above all, but if not, the button should be autonomus and decide on
|
||||
// its own to be visible or not.
|
||||
const isModerator = isLocalParticipantModerator(state);
|
||||
const {
|
||||
enableFeaturesBasedOnToken,
|
||||
liveStreamingEnabled
|
||||
} = state['features/base/config'];
|
||||
const { features = {} } = getLocalParticipant(state);
|
||||
|
||||
visible = liveStreamingEnabled;
|
||||
visible = isModerator && liveStreamingEnabled;
|
||||
|
||||
if (enableFeaturesBasedOnToken) {
|
||||
visible = visible && String(features.livestreaming) === 'true';
|
||||
@@ -140,13 +144,7 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
if (!visible && !_disabled) {
|
||||
_disabled = true;
|
||||
visible = true;
|
||||
|
||||
// button and tooltip
|
||||
if (state['features/base/jwt'].isGuest) {
|
||||
_tooltip = 'dialog.liveStreamingDisabledForGuestTooltip';
|
||||
} else {
|
||||
_tooltip = 'dialog.liveStreamingDisabledTooltip';
|
||||
}
|
||||
_tooltip = 'dialog.liveStreamingDisabledTooltip';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,13 +153,7 @@ export function _mapStateToProps(state: Object, ownProps: Props): Object {
|
||||
if (!visible && !_disabled) {
|
||||
_disabled = true;
|
||||
visible = true;
|
||||
|
||||
// button and tooltip
|
||||
if (state['features/base/jwt'].isGuest) {
|
||||
_tooltip = 'dialog.recordingDisabledForGuestTooltip';
|
||||
} else {
|
||||
_tooltip = 'dialog.recordingDisabledTooltip';
|
||||
}
|
||||
_tooltip = 'dialog.recordingDisabledTooltip';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconKick } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import AbstractKickButton, {
|
||||
type Props
|
||||
} from '../AbstractKickButton';
|
||||
@@ -42,11 +41,7 @@ class KickButton extends AbstractKickButton {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { participantID, t, visible } = this.props;
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
const { participantID, t } = this.props;
|
||||
|
||||
return (
|
||||
<RemoteVideoMenuButton
|
||||
@@ -61,22 +56,4 @@ class KickButton extends AbstractKickButton {
|
||||
|
||||
_handleClick: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to {@link KickButton}'s React {@code Component}
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
const shouldHide = interfaceConfig.HIDE_KICK_BUTTON_FOR_GUESTS && state['features/base/jwt'].isGuest;
|
||||
|
||||
return {
|
||||
visible: !shouldHide
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(KickButton));
|
||||
|
||||
export default translate(KickButton);
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Icon, IconMenuThumb } from '../../../base/icons';
|
||||
import { MEDIA_TYPE } from '../../../base/media';
|
||||
import { getLocalParticipant, PARTICIPANT_ROLE } from '../../../base/participants';
|
||||
import { Popover } from '../../../base/popover';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { isRemoteTrackMuted } from '../../../base/tracks';
|
||||
|
||||
import {
|
||||
GrantModeratorButton,
|
||||
@@ -37,6 +39,11 @@ type Props = {
|
||||
*/
|
||||
_disableRemoteMute: Boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the participant is currently muted.
|
||||
*/
|
||||
_isAudioMuted: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the participant is a conference moderator.
|
||||
*/
|
||||
@@ -48,11 +55,6 @@ type Props = {
|
||||
*/
|
||||
initialVolumeValue: number,
|
||||
|
||||
/**
|
||||
* Whether or not the participant is currently muted.
|
||||
*/
|
||||
isAudioMuted: boolean,
|
||||
|
||||
/**
|
||||
* Callback to invoke when the popover has been displayed.
|
||||
*/
|
||||
@@ -170,9 +172,9 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||
const {
|
||||
_disableKick,
|
||||
_disableRemoteMute,
|
||||
_isAudioMuted,
|
||||
_isModerator,
|
||||
initialVolumeValue,
|
||||
isAudioMuted,
|
||||
onRemoteControlToggle,
|
||||
onVolumeChange,
|
||||
remoteControlState,
|
||||
@@ -185,7 +187,7 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||
if (!_disableRemoteMute) {
|
||||
buttons.push(
|
||||
<MuteButton
|
||||
isAudioMuted = { isAudioMuted }
|
||||
isAudioMuted = { _isAudioMuted }
|
||||
key = 'mute'
|
||||
participantID = { participantID } />
|
||||
);
|
||||
@@ -258,13 +260,16 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||
* _isModerator: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const participant = getLocalParticipant(state);
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
const { participantID } = ownProps;
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const { remoteVideoMenu = {}, disableRemoteMute } = state['features/base/config'];
|
||||
const { disableKick } = remoteVideoMenu;
|
||||
|
||||
return {
|
||||
_isModerator: Boolean(participant?.role === PARTICIPANT_ROLE.MODERATOR),
|
||||
_isAudioMuted: isRemoteTrackMuted(tracks, MEDIA_TYPE.AUDIO, participantID) || false,
|
||||
_isModerator: Boolean(localParticipant?.role === PARTICIPANT_ROLE.MODERATOR),
|
||||
_disableKick: Boolean(disableKick),
|
||||
_disableRemoteMute: Boolean(disableRemoteMute)
|
||||
};
|
||||
|
||||
@@ -154,9 +154,15 @@ function PasswordSection({
|
||||
<a
|
||||
className = 'remove-password'
|
||||
onClick = { onPasswordRemove }>{ t('dialog.Remove') }</a>
|
||||
<a
|
||||
className = 'copy-password'
|
||||
onClick = { onPasswordCopy }>{ t('dialog.copy') }</a>
|
||||
{
|
||||
|
||||
// There are cases like lobby and grant moderator when password is not available
|
||||
password ? <>
|
||||
<a
|
||||
className = 'copy-password'
|
||||
onClick = { onPasswordCopy }>{ t('dialog.copy') }</a>
|
||||
</> : null
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -125,13 +125,10 @@ function mapStateToProps(state) {
|
||||
locked,
|
||||
password
|
||||
} = state['features/base/conference'];
|
||||
const {
|
||||
lockRoomGuestEnabled,
|
||||
roomPasswordNumberOfDigits
|
||||
} = state['features/base/config'];
|
||||
const { roomPasswordNumberOfDigits } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_canEditPassword: isLocalParticipantModerator(state, lockRoomGuestEnabled),
|
||||
_canEditPassword: isLocalParticipantModerator(state),
|
||||
_conference: conference,
|
||||
_dialIn: state['features/invite'],
|
||||
_locked: locked,
|
||||
|
||||
@@ -126,14 +126,13 @@ class SettingsDialog extends Component<Props> {
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const configuredTabs = interfaceConfig.SETTINGS_SECTIONS || [];
|
||||
const jwt = state['features/base/jwt'];
|
||||
|
||||
// The settings sections to display.
|
||||
const showDeviceSettings = configuredTabs.includes('devices');
|
||||
const moreTabProps = getMoreTabProps(state);
|
||||
const { showModeratorSettings, showLanguageSettings, showPrejoinSettings } = moreTabProps;
|
||||
const showProfileSettings
|
||||
= configuredTabs.includes('profile') && jwt.isGuest;
|
||||
= configuredTabs.includes('profile') && !state['features/base/config'].disableProfile;
|
||||
const showCalendarSettings
|
||||
= configuredTabs.includes('calendar') && isCalendarEnabled(state);
|
||||
const tabs = [];
|
||||
|
||||
@@ -141,7 +141,14 @@ export default class JitsiStreamPresenterEffect {
|
||||
timeMs: 1000 / this._frameRate
|
||||
});
|
||||
|
||||
return this._canvas.captureStream(this._frameRate);
|
||||
const capturedStream = this._canvas.captureStream(this._frameRate);
|
||||
|
||||
// Put emphasis on the text details for the presenter's stream
|
||||
// See https://www.w3.org/TR/mst-content-hint/
|
||||
// $FlowExpectedError
|
||||
capturedStream.getVideoTracks()[0].contentHint = 'text';
|
||||
|
||||
return capturedStream;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||