Compare commits

..

1 Commits

Author SHA1 Message Date
Aaron van Meerten
a49b6140e0 feature: empty token verification allow list 2022-12-15 09:02:48 -06:00
59 changed files with 789 additions and 741 deletions

View File

@@ -7,7 +7,7 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 16
@@ -20,11 +20,12 @@ jobs:
- name: Check if the git repository is clean
run: $(exit $(git status --porcelain --untracked-files=no | head -255 | wc -l)) || (echo "Dirty git tree"; git diff; exit 1)
- run: npm run lint:ci
- run: for file in lang/*.json; do npx --yes jsonlint -q $file || exit 1; done
linux-build:
name: Build Frontend (Linux)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 16
@@ -35,7 +36,7 @@ jobs:
name: Build Frontend (macOS)
runs-on: macOS-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 16

View File

@@ -81,8 +81,6 @@ public class JitsiMeetView extends FrameLayout {
result.putBoolean(key, (Boolean)bValue);
} else if (valueType.contentEquals("String")) {
result.putString(key, (String)bValue);
} else if (valueType.contentEquals("Integer")) {
result.putInt(key, (int)bValue);
} else if (valueType.contentEquals("Bundle")) {
result.putBundle(key, mergeProps((Bundle)aValue, (Bundle)bValue));
} else {

View File

@@ -2342,7 +2342,7 @@ export default {
* @param {MediaDeviceInfo[]} devices
* @returns {Promise}
*/
async _onDeviceListChanged(devices) {
_onDeviceListChanged(devices) {
const oldDevices = APP.store.getState()['features/base/devices'].availableDevices;
const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
const localVideo = getLocalJitsiVideoTrack(APP.store.getState());
@@ -2356,10 +2356,13 @@ export default {
const newDevices
= mediaDeviceHelper.getNewMediaDevicesAfterDeviceListChanged(
devices,
this.isSharingScreen,
localVideo,
localAudio,
newLabelsOnly);
const promises = [];
const audioWasMuted = this.isLocalAudioMuted();
const videoWasMuted = this.isLocalVideoMuted();
const requestedInput = {
audio: Boolean(newDevices.audioinput),
video: Boolean(newDevices.videoinput)
@@ -2371,6 +2374,7 @@ export default {
= setAudioOutputDeviceId(newDevices.audiooutput, dispatch)
.catch(); // Just ignore any errors in catch block.
promises.push(setAudioOutputPromise);
}
@@ -2387,7 +2391,8 @@ export default {
}
// Let's handle unknown/non-preferred devices
const newAvailDevices = APP.store.getState()['features/base/devices'].availableDevices;
const newAvailDevices
= APP.store.getState()['features/base/devices'].availableDevices;
let newAudioDevices = [];
let oldAudioDevices = [];
@@ -2403,85 +2408,103 @@ export default {
// check for audio
if (newAudioDevices.length > 0) {
APP.store.dispatch(checkAndNotifyForNewDevice(newAudioDevices, oldAudioDevices));
APP.store.dispatch(
checkAndNotifyForNewDevice(newAudioDevices, oldAudioDevices));
}
// check for video
if (!requestedInput.video) {
APP.store.dispatch(checkAndNotifyForNewDevice(newAvailDevices.videoInput, oldDevices.videoInput));
APP.store.dispatch(
checkAndNotifyForNewDevice(newAvailDevices.videoInput, oldDevices.videoInput));
}
// When the 'default' mic needs to be selected, we need to pass the real device id to gUM instead of 'default'
// in order to get the correct MediaStreamTrack from chrome because of the following bug.
// When the 'default' mic needs to be selected, we need to
// pass the real device id to gUM instead of 'default' in order
// to get the correct MediaStreamTrack from chrome because of the
// following bug.
// https://bugs.chromium.org/p/chromium/issues/detail?id=997689
const hasDefaultMicChanged = newDevices.audioinput === 'default';
// When the local video is muted and a preferred device is connected, update the settings and remove the track
// from the conference. A new track will be created and replaced when the user unmutes their camera.
// This is the case when the local video is muted and a preferred device is connected.
if (requestedInput.video && this.isLocalVideoMuted()) {
APP.store.dispatch(updateSettings({
// 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
}));
requestedInput.video = false;
delete newDevices.videoinput;
// Remove the track from the conference.
if (localVideo) {
await this.useVideoStream(null);
logger.debug('_onDeviceListChanged: Removed the current video track.');
}
// Removing the current video track in order to force the unmute to select the preferred device.
logger.debug('_onDeviceListChanged: Removing the current video track.');
this.useVideoStream(null);
}
// When the local audio is muted and a preferred device is connected, update the settings and remove the track
// from the conference. A new track will be created and replaced when the user unmutes their mic.
if (requestedInput.audio && this.isLocalAudioMuted()) {
APP.store.dispatch(updateSettings({
micDeviceId: newDevices.audioinput
}));
requestedInput.audio = false;
delete newDevices.audioinput;
// Remove the track from the conference.
if (localAudio) {
await this.useAudioStream(null);
logger.debug('_onDeviceListChanged: Removed the current audio track.');
}
}
// Create the tracks and replace them only if the user is unmuted.
if (requestedInput.audio || requestedInput.video) {
let tracks = [];
try {
tracks = await mediaDeviceHelper.createLocalTracksAfterDeviceListChanged(
promises.push(
mediaDeviceHelper.createLocalTracksAfterDeviceListChanged(
createLocalTracksF,
newDevices.videoinput,
hasDefaultMicChanged
? getDefaultDeviceId(APP.store.getState(), 'audioInput')
: newDevices.audioinput);
} catch (error) {
logger.error(`Track creation failed on device change, ${error}`);
: newDevices.audioinput)
.then(tracks => {
// If audio or video muted before, or we unplugged current
// device and selected new one, then mute new track.
const muteSyncPromises = tracks.map(track => {
if ((track.isVideoTrack() && videoWasMuted)
|| (track.isAudioTrack() && audioWasMuted)) {
return track.mute();
}
return Promise.reject(error);
}
return Promise.resolve();
});
for (const track of tracks) {
if (track.isAudioTrack()) {
promises.push(
this.useAudioStream(track)
.then(() => {
hasDefaultMicChanged && (track._realDeviceId = track.deviceId = 'default');
this._updateAudioDeviceId();
}));
} else {
promises.push(
this.useVideoStream(track)
.then(() => {
this._updateVideoDeviceId();
}));
}
}
}
return Promise.all(muteSyncPromises)
.then(() =>
Promise.all(Object.keys(requestedInput).map(mediaType => {
if (requestedInput[mediaType]) {
const useStream
= mediaType === 'audio'
? this.useAudioStream.bind(this)
: this.useVideoStream.bind(this);
const track = tracks.find(t => t.getType() === mediaType) || null;
// Use the new stream or null if we failed to obtain it.
return useStream(track)
.then(() => {
if (track?.isAudioTrack() && hasDefaultMicChanged) {
// workaround for the default device to be shown as selected in the
// settings even when the real device id was passed to gUM because of
// the above mentioned chrome bug.
track._realDeviceId = track.deviceId = 'default';
}
mediaType === 'audio'
? this._updateAudioDeviceId()
: this._updateVideoDeviceId();
});
}
return Promise.resolve();
})));
})
.then(() => {
// Log and sync known mute state.
if (audioWasMuted) {
sendAnalytics(createTrackMutedEvent(
'audio',
'device list changed'));
logger.log('Audio mute: device list changed');
muteLocalAudio(true);
}
if (!this.isSharingScreen && videoWasMuted) {
sendAnalytics(createTrackMutedEvent(
'video',
'device list changed'));
logger.log('Video mute: device list changed');
muteLocalVideo(true);
}
}));
return Promise.all(promises)
.then(() => {

View File

@@ -262,6 +262,17 @@ var config = {
// applied locally. FIXME: having these 2 options is confusing.
// startWithVideoMuted: false,
// If set to true, prefer to use the H.264 video codec (if supported).
// Note that it's not recommended to do this because simulcast is not
// supported when using H.264. For 1-to-1 calls this setting is enabled by
// default and can be toggled in the p2p section.
// This option has been deprecated, use preferredCodec under videoQuality section instead.
// preferH264: true,
// If set to true, disable H.264 video codec by stripping it out of the
// SDP.
// disableH264: false,
// Desktop sharing
// Optional desktop sharing frame rate options. Default value: min:5, max:5.
@@ -912,10 +923,18 @@ var config = {
// If not set, the effective value is 'all'.
// iceTransportPolicy: 'all',
// If set to true, it will prefer to use H.264 for P2P calls (if H.264
// is supported). This setting is deprecated, use preferredCodec instead.
// preferH264: true,
// Provides a way to set the video codec preference on the p2p connection. Acceptable
// codec values are 'VP8', 'VP9' and 'H264'.
// preferredCodec: 'H264',
// If set to true, disable H.264 video codec by stripping it out of the
// SDP. This setting is deprecated, use disabledCodec instead.
// disableH264: false,
// Provides a way to prevent a video codec from being negotiated on the p2p connection.
// disabledCodec: '',
@@ -1072,67 +1091,10 @@ var config = {
// use only.
// _desktopSharingSourceDevice: 'sample-id-or-label',
// DEPRECATED! Use deeplinking.disabled instead.
// If true, any checks to handoff to another application will be prevented
// and instead the app will continue to display in the current browser.
// disableDeepLinking: false,
// The deeplinking config.
// For information about the properties of
// deeplinking.[ios/android].dynamicLink check:
// https://firebase.google.com/docs/dynamic-links/create-manually
// deeplinking: {
//
// // The desktop deeplinking config.
// desktop: {
// appName: 'Jitsi Meet'
// },
// // If true, any checks to handoff to another application will be prevented
// // and instead the app will continue to display in the current browser.
// disabled: false,
// // whether to hide the logo on the deep linking pages.
// hideLogo: false,
// // whether to show deeplinking image.
// showImage: false,
// // The ios deeplinking config.
// ios: {
// appName: 'Jitsi Meet',
// // Specify mobile app scheme for opening the app from the mobile browser.
// appScheme: 'org.jitsi.meet',
// // Custom URL for downloading ios mobile app.
// downloadLink: 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905',
// dynamicLink: {
// apn: 'org.jitsi.meet',
// appCode: 'w2atb',
// customDomain: undefined,
// ibi: 'com.atlassian.JitsiMeet.ios',
// isi: '1165103905'
// }
// },
// // The android deeplinking config.
// android: {
// appName: 'Jitsi Meet',
// // Specify mobile app scheme for opening the app from the mobile browser.
// appScheme: 'org.jitsi.meet',
// // Custom URL for downloading android mobile app.
// downloadLink: 'https://play.google.com/store/apps/details?id=org.jitsi.meet',
// // Android app package name.
// appPackage: 'org.jitsi.meet',
// fDroidUrl: 'https://f-droid.org/en/packages/org.jitsi.meet/',
// dynamicLink: {
// apn: 'org.jitsi.meet',
// appCode: 'w2atb',
// customDomain: undefined,
// ibi: 'com.atlassian.JitsiMeet.ios',
// isi: '1165103905'
// }
// }
// },
// A property to disable the right click context menu for localVideo
// the menu has option to flip the locally seen video for local presentations
// disableLocalVideoFlip: false,

View File

@@ -126,12 +126,6 @@ form {
background-size: contain;
}
.leftwatermarknomargin {
background-position: center left;
background-repeat: no-repeat;
background-size: contain;
}
.rightwatermark {
right: 32px;
top: 32px;

View File

@@ -170,9 +170,8 @@ $welcomePageHeaderPaddingBottom: 0px;
$welcomePageHeaderTitleMaxWidth: initial;
$welcomePageHeaderTextAlign: center;
$welcomePageHeaderContainerMarginTop: 104px;
$welcomePageHeaderContainerDisplay: flex;
$welcomePageHeaderContainerMargin: $welcomePageHeaderContainerMarginTop auto 0;
$welcomePageHeaderContainerMargin: 104px 32px 0 32px;
$welcomePageHeaderTextTitleMarginBottom: 0;
$welcomePageHeaderTextTitleFontSize: 42px;

View File

@@ -29,16 +29,6 @@ body.welcome-page {
flex-direction: column;
margin: $welcomePageHeaderContainerMargin;
z-index: $zindex2;
align-items: center;
position: relative;
max-width: 688px;
}
.header-watermark-container {
position: absolute;
width: 100%;
height: 100%;
margin-top: calc(20px - #{$welcomePageHeaderContainerMarginTop});
}
.header-text-title {
@@ -133,11 +123,16 @@ body.welcome-page {
max-width: calc(100% - 40px);
padding: 16px 0 39px 0;
width: $welcomePageEnterRoomWidth;
text-align: center;
a {
color: inherit;
font-weight: 600;
p {
color: $welcomePageDescriptionColor;
float: left;
text-align: $welcomePageHeaderTextAlign;
a {
color: inherit;
font-weight: 600;
}
}
}
}
@@ -205,8 +200,8 @@ body.welcome-page {
color: $welcomePageDescriptionColor;
padding: 4px;
position: absolute;
top: calc(35px - #{$welcomePageHeaderContainerMarginTop});
right: 0;
top: 32px;
right: 32px;
z-index: $zindex2;
* {
@@ -229,11 +224,6 @@ body.welcome-page {
width: $welcomePageWatermarkWidth;
height: $welcomePageWatermarkHeight;
}
.watermark.leftwatermarknomargin {
width: $welcomePageWatermarkWidth;
height: $welcomePageWatermarkHeight;
}
}
&.without-content {
@@ -252,17 +242,10 @@ body.welcome-page {
padding-top: 40px;
}
.welcome-card-column {
.welcome-card-row {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
max-width: 688px;
margin: auto;
> div {
margin-bottom: 16px;
}
padding: 0 32px;
}
.welcome-card-text {
@@ -270,7 +253,7 @@ body.welcome-page {
}
.welcome-card {
width: 100%;
width: 49%;
border-radius: 8px;
&--dark {
@@ -285,6 +268,10 @@ body.welcome-page {
&--grey {
background: #F2F3F4;
}
&--shadow {
box-shadow: 0px 4px 30px rgba(0, 0, 0, 0.15);
}
}
.welcome-footer {

View File

@@ -76,6 +76,11 @@ var interfaceConfig = {
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
/**
* Hide the logo on the deep linking pages.
*/
HIDE_DEEP_LINKING_LOGO: false,
/**
* Hide the invite prompt in the header when alone in the meeting.
*/
@@ -103,6 +108,23 @@ 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
// has a suboptimal experience. Browsers which are not listed as optimal or
// unsupported are considered suboptimal. Valid values are:
@@ -137,6 +159,7 @@ var interfaceConfig = {
*/
SHOW_CHROME_EXTENSION_BANNER: false,
SHOW_DEEP_LINKING_IMAGE: false,
SHOW_JITSI_WATERMARK: true,
SHOW_POWERED_BY: false,
SHOW_PROMOTIONAL_CLOSE_PAGE: false,
@@ -177,33 +200,6 @@ var interfaceConfig = {
*/
// TILE_VIEW_MAX_COLUMNS: 5,
// List of undocumented settings
/**
INDICATOR_FONT_SIZES
PHONE_NUMBER_REGEX
*/
// -----------------DEPRECATED CONFIGS BELOW THIS LINE-----------------------------
/**
* Specify URL for downloading ios mobile app.
*/
// MOBILE_DOWNLOAD_LINK_IOS: 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905',
/**
* Specify custom URL for downloading android mobile app.
*/
// MOBILE_DOWNLOAD_LINK_ANDROID: 'https://play.google.com/store/apps/details?id=org.jitsi.meet',
// SHOW_DEEP_LINKING_IMAGE: false,
/**
* Specify mobile app scheme for opening the app from the mobile browser.
*/
// APP_SCHEME: 'org.jitsi.meet',
// NATIVE_APP_NAME: 'Jitsi Meet',
/**
* Specify Firebase dynamic link properties for the mobile apps.
*/
@@ -216,19 +212,22 @@ var interfaceConfig = {
// },
/**
* Hide the logo on the deep linking pages.
* Specify mobile app scheme for opening the app from the mobile browser.
*/
// HIDE_DEEP_LINKING_LOGO: false,
// APP_SCHEME: 'org.jitsi.meet',
/**
* Specify the Android app package name.
*/
// ANDROID_APP_PACKAGE: 'org.jitsi.meet',
// List of undocumented settings
/**
* Specify custom URL for downloading f droid app.
*/
// MOBILE_DOWNLOAD_LINK_F_DROID: 'https://f-droid.org/en/packages/org.jitsi.meet/',
INDICATOR_FONT_SIZES
PHONE_NUMBER_REGEX
*/
// -----------------DEPRECATED CONFIGS BELOW THIS LINE-----------------------------
// Connection indicators (
// CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,

View File

@@ -385,7 +385,7 @@ PODS:
- react-native-video/Video (6.0.0-alpha.1):
- PromisesSwift
- React-Core
- react-native-webrtc (106.0.1):
- react-native-webrtc (106.0.0):
- JitsiWebRTC (~> 106.0.0)
- React-Core
- react-native-webview (11.15.1):
@@ -755,7 +755,7 @@ SPEC CHECKSUMS:
react-native-slider: 6e9b86e76cce4b9e35b3403193a6432ed07e0c81
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
react-native-video: bb6f12a7198db53b261fefb5d609dc77417acc8b
react-native-webrtc: aa3a0fdc4c410813892b97d18947f223d3e50f0c
react-native-webrtc: 0a407105bf428c9157f2e8d4d6f7c844dc185933
react-native-webview: ea4899a1056c782afa96dd082179a66cbebf5504
React-perflogger: 0458a87ea9a7342079e7a31b0d32b3734fb8415f
React-RCTActionSheet: 22538001ea2926dea001111dd2846c13a0730bc9

View File

@@ -147,7 +147,6 @@
"bridgeCount": "Serverzahl: ",
"codecs": "Codecs (A/V): ",
"connectedTo": "Verbunden mit:",
"e2eeVerified": "E2EE verifiziert:",
"framerate": "Bildwiederholrate:",
"less": "Weniger anzeigen",
"localaddress": "Lokale Adresse:",
@@ -409,10 +408,6 @@
"user": "Anmeldename",
"userIdentifier": "Benutzername",
"userPassword": "Passwort",
"verifyParticipantConfirm": "Sie stimmen überein",
"verifyParticipantDismiss": "Sie stimmen nicht überein",
"verifyParticipantQuestion": "EXPERIMENTELL: Frage Person {{participantName}} ob sie den selben Inhalt in der selben Reihenfolge sieht.",
"verifyParticipantTitle": "Personsverifikation",
"videoLink": "Video-Link",
"viewUpgradeOptions": "Upgradeoptionen anzeigen",
"viewUpgradeOptionsContent": "Sie müssen Ihren Tarif erweitern, um Premium-Features wie Aufnahme, Transkription, RTMP-Streaming und mehr zu nutzen.",
@@ -442,6 +437,9 @@
"noResults": "Keine Ergebnisse :(",
"search": "GIPHY durchsuchen"
},
"helpView": {
"title": "Hilfecenter"
},
"incomingCall": {
"answer": "Antworten",
"audioCallTitle": "Eingehender Anruf",
@@ -565,6 +563,7 @@
"lobby": {
"admit": "Zulassen",
"admitAll": "Alle zulassen",
"allow": "Annehmen",
"backToKnockModeButton": "Kein Passwort, stattdessen Beitritt anfragen",
"chat": "Chat",
"dialogTitle": "Lobbymodus",
@@ -650,8 +649,6 @@
"connectedOneMember": "{{name}} nimmt am Meeting teil",
"connectedThreePlusMembers": "{{name}} und {{count}} andere Personen nehmen am Meeting teil",
"connectedTwoMembers": "{{first}} und {{second}} nehmen am Meeting teil",
"dataChannelClosed": "Schlechte Videoqualität",
"dataChannelClosedDescription": "Die Steuerungsverbindung (Bridge Channel) wurde unterbrochen, daher ist die Videoqulität auf die schlechteste Stufe limitiert.",
"disconnected": "getrennt",
"displayNotifications": "Benachrichtigungen anzeigen für",
"focus": "Konferenzleitung",
@@ -712,8 +709,6 @@
"reactionSoundsForAll": "Interaktionstöne für alle deaktivieren",
"screenShareNoAudio": "Die Option \"Audio freigeben\" wurde bei der Auswahl des Fensters nicht ausgewählt.",
"screenShareNoAudioTitle": "Share audio was not checked",
"screenSharingAudioOnlyDescription": "Durch die Bildschirmfreigabe wird der Modus \"Beste Leistung\" beeinflusst und daher mehr Datenrate benötigt.",
"screenSharingAudioOnlyTitle": "Modus \"Beste Leistung\"",
"selfViewTitle": "Sie können die eigene Ansicht immer in den Einstellungen reaktivieren",
"somebody": "Jemand",
"startSilentDescription": "Treten Sie dem Meeting noch einmal bei, um Ihr Audio zu aktivieren",
@@ -863,6 +858,9 @@
"rejected": "Abgelehnt",
"ringing": "Es klingelt …"
},
"privacyView": {
"title": "Datenschutz"
},
"profile": {
"avatar": "Benutzerbild",
"setDisplayNameLabel": "Anzeigename festlegen",
@@ -1005,7 +1003,6 @@
"displayName": "Anzeigename",
"displayNamePlaceholderText": "z.B. Erika Musterfrau",
"email": "E-Mail",
"emailPlaceholderText": "email@beispiel.de",
"goTo": "Gehe zu",
"header": "Einstellungen",
"help": "Hilfe",
@@ -1014,7 +1011,6 @@
"profileSection": "Profil",
"serverURL": "Server-URL",
"showAdvanced": "Erweiterte Einstellungen anzeigen",
"startCarModeInLowBandwidthMode": "Automodus mit Datensparmodus starten",
"startWithAudioMuted": "Stumm beitreten",
"startWithVideoMuted": "Ohne Video beitreten",
"terms": "Nutzungsbedingungen",
@@ -1294,7 +1290,6 @@
"show": "Im Vordergrund anzeigen",
"showSelfView": "Eigene Ansicht anzeigen",
"unpinFromStage": "Lösen",
"verify": "Person verifizieren",
"videoMuted": "Kamera ausgeschaltet",
"videomute": "Person hat die Kamera angehalten"
},
@@ -1362,7 +1357,6 @@
"recentList": "Verlauf",
"recentListDelete": "Eintrag löschen",
"recentListEmpty": "Ihr Konferenzverlauf ist derzeit leer. Reden Sie mit Ihrem Team und Ihre vergangenen Konferenzen landen hier.",
"recentMeetings": "Ihre letzten Konferenzen",
"reducedUIText": "Willkommen bei {{app}}!",
"roomNameAllowedChars": "Der Konferenzname sollte keines der folgenden Zeichen enthalten: ?, &, :, ', \", %, #.",
"roomname": "Konferenzname eingeben",
@@ -1371,7 +1365,6 @@
"settings": "Einstellungen",
"startMeeting": "Meeting starten",
"terms": "AGB",
"title": "Sichere, voll funktionale und komplett kostenlose Videokonferenzen",
"upcomingMeetings": "Ihre zukünftigen Konferenzen"
"title": "Sichere, voll funktionale und komplett kostenlose Videokonferenzen"
}
}

View File

@@ -442,6 +442,9 @@
"noResults": "No results found :(",
"search": "Search GIPHY"
},
"helpView": {
"title": "Help center"
},
"incomingCall": {
"answer": "Answer",
"audioCallTitle": "Incoming call",
@@ -712,8 +715,6 @@
"reactionSoundsForAll": "Disable sounds for all",
"screenShareNoAudio": "Share audio box was not checked in the window selection screen.",
"screenShareNoAudioTitle": "Couldn't share system audio!",
"screenSharingAudioOnlyDescription": "Please note that by sharing your screen you're affecting the \"Best performance\" mode and you will use more bandwidth.",
"screenSharingAudioOnlyTitle": "\"Best performance\" mode",
"selfViewTitle": "You can always un-hide the self-view from settings",
"somebody": "Somebody",
"startSilentDescription": "Rejoin the meeting to enable audio",
@@ -863,6 +864,9 @@
"rejected": "Rejected",
"ringing": "Ringing..."
},
"privacyView": {
"title": "Privacy"
},
"profile": {
"avatar": "avatar",
"setDisplayNameLabel": "Set your display name",
@@ -1362,7 +1366,6 @@
"recentList": "Recent",
"recentListDelete": "Delete entry",
"recentListEmpty": "Your recent list is currently empty. Chat with your team and you will find all your recent meetings here.",
"recentMeetings": "Your recent meetings",
"reducedUIText": "Welcome to {{app}}!",
"roomNameAllowedChars": "Meeting name should not contain any of these characters: ?, &, :, ', \", %, #.",
"roomname": "Enter room name",
@@ -1371,7 +1374,6 @@
"settings": "Settings",
"startMeeting": "Start meeting",
"terms": "Terms",
"title": "Secure, fully featured, and completely free video conferencing",
"upcomingMeetings": "Your upcoming meetings"
"title": "Secure, fully featured, and completely free video conferencing"
}
}

View File

@@ -158,6 +158,7 @@ export default {
* Determines if currently selected media devices should be changed after
* list of available devices has been changed.
* @param {MediaDeviceInfo[]} newDevices
* @param {boolean} isSharingScreen
* @param {JitsiLocalTrack} localVideo
* @param {JitsiLocalTrack} localAudio
* @returns {{
@@ -168,12 +169,13 @@ export default {
*/
getNewMediaDevicesAfterDeviceListChanged( // eslint-disable-line max-params
newDevices,
isSharingScreen,
localVideo,
localAudio,
newLabels) {
return {
audioinput: getNewAudioInputDevice(newDevices, localAudio, newLabels),
videoinput: getNewVideoInputDevice(newDevices, localVideo, newLabels),
videoinput: isSharingScreen ? undefined : getNewVideoInputDevice(newDevices, localVideo, newLabels),
audiooutput: getNewAudioOutputDevice(newDevices)
};
},

60
package-lock.json generated
View File

@@ -74,7 +74,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1561.0.0+2d4cd935/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1557.0.0+8df33524/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -114,7 +114,7 @@
"react-native-url-polyfill": "1.3.0",
"react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
"react-native-watch-connectivity": "1.0.11",
"react-native-webrtc": "106.0.1",
"react-native-webrtc": "106.0.0",
"react-native-webview": "11.15.1",
"react-native-youtube-iframe": "2.2.1",
"react-redux": "7.1.0",
@@ -13497,8 +13497,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1561.0.0+2d4cd935/lib-jitsi-meet.tgz",
"integrity": "sha512-ec3XE3LheQQEkIBZ8mrfGdRDE/9yKM/CQpw7E2eF1zMQJGGREZRZ+Xqbhgv7o1eYPgI5GsAECns8ZjmWtre8bg==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1557.0.0+8df33524/lib-jitsi-meet.tgz",
"integrity": "sha512-LH24V3aCAyNxrXkYsr4Syz9G+hDyfGo7JUu3suEbh1bS5Y4w8mwzTBHyGazLnD7/NDG5F783DY19/yCzR7stSQ==",
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.0.0",
@@ -13571,9 +13571,9 @@
}
},
"node_modules/loader-utils": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
"integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.1.tgz",
"integrity": "sha512-1Qo97Y2oKaU+Ro2xnDMR26g1BwMT29jNbem1EvcujW2jqt+j5COXyscjM7bLQkM9HaxI7pkWeW7gnI072yMI9Q==",
"dev": true,
"dependencies": {
"big.js": "^5.2.2",
@@ -14711,9 +14711,9 @@
}
},
"node_modules/null-loader/node_modules/loader-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz",
"integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==",
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
@@ -16501,9 +16501,9 @@
}
},
"node_modules/react-native-webrtc": {
"version": "106.0.1",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-106.0.1.tgz",
"integrity": "sha512-0l911lDIqj7jKMvxwQEF6mQt6CMnmOZjgwyifNT28w3XY4p+0Tm/mg5S0UqjbFzcUWeI5w2q/Xk7zn0mcbEhPg==",
"version": "106.0.0",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-106.0.0.tgz",
"integrity": "sha512-nFl8WSNGMNxuIiaNAiJvILRcEC65yRxPOWTexLrM+vo44syt/4chEvzN9eOqXiPsOmsECQmwZupCUGR5XABUNg==",
"hasInstallScript": true,
"dependencies": {
"adm-zip": "0.5.9",
@@ -18177,9 +18177,9 @@
}
},
"node_modules/string-replace-loader/node_modules/loader-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz",
"integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==",
"dev": true,
"dependencies": {
"big.js": "^5.2.2",
@@ -30496,8 +30496,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1561.0.0+2d4cd935/lib-jitsi-meet.tgz",
"integrity": "sha512-ec3XE3LheQQEkIBZ8mrfGdRDE/9yKM/CQpw7E2eF1zMQJGGREZRZ+Xqbhgv7o1eYPgI5GsAECns8ZjmWtre8bg==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1557.0.0+8df33524/lib-jitsi-meet.tgz",
"integrity": "sha512-LH24V3aCAyNxrXkYsr4Syz9G+hDyfGo7JUu3suEbh1bS5Y4w8mwzTBHyGazLnD7/NDG5F783DY19/yCzR7stSQ==",
"requires": {
"@jitsi/js-utils": "2.0.0",
"@jitsi/logger": "2.0.0",
@@ -30565,9 +30565,9 @@
"dev": true
},
"loader-utils": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
"integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.1.tgz",
"integrity": "sha512-1Qo97Y2oKaU+Ro2xnDMR26g1BwMT29jNbem1EvcujW2jqt+j5COXyscjM7bLQkM9HaxI7pkWeW7gnI072yMI9Q==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
@@ -31474,9 +31474,9 @@
},
"dependencies": {
"loader-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz",
"integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==",
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
@@ -32790,9 +32790,9 @@
}
},
"react-native-webrtc": {
"version": "106.0.1",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-106.0.1.tgz",
"integrity": "sha512-0l911lDIqj7jKMvxwQEF6mQt6CMnmOZjgwyifNT28w3XY4p+0Tm/mg5S0UqjbFzcUWeI5w2q/Xk7zn0mcbEhPg==",
"version": "106.0.0",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-106.0.0.tgz",
"integrity": "sha512-nFl8WSNGMNxuIiaNAiJvILRcEC65yRxPOWTexLrM+vo44syt/4chEvzN9eOqXiPsOmsECQmwZupCUGR5XABUNg==",
"requires": {
"adm-zip": "0.5.9",
"base64-js": "1.5.1",
@@ -34086,9 +34086,9 @@
},
"dependencies": {
"loader-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz",
"integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==",
"dev": true,
"requires": {
"big.js": "^5.2.2",

View File

@@ -79,7 +79,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1561.0.0+2d4cd935/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1557.0.0+8df33524/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -119,7 +119,7 @@
"react-native-url-polyfill": "1.3.0",
"react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
"react-native-watch-connectivity": "1.0.11",
"react-native-webrtc": "106.0.1",
"react-native-webrtc": "106.0.0",
"react-native-webview": "11.15.1",
"react-native-youtube-iframe": "2.2.1",
"react-redux": "7.1.0",
@@ -199,8 +199,7 @@
"tsc:web": "tsc --noEmit --project tsconfig.web.json",
"tsc:native": "tsc --noEmit --project tsconfig.native.json",
"tsc:ci": "npm run tsc:web && npm run tsc:native",
"lint:ci": "eslint --ext .js,.ts,.tsx --max-warnings 0 . && npm run tsc:ci && npm run lint:lang",
"lint:lang": "for file in lang/*.json; do npx --yes jsonlint -q $file || exit 1; done",
"lint:ci": "eslint --ext .js,.ts,.tsx --max-warnings 0 . && npm run tsc:ci",
"lang-sort": "./resources/lang-sort.sh",
"lint-fix": "eslint --ext .js,.ts,.tsx --max-warnings 0 --fix .",
"postinstall": "patch-package --error-on-fail && jetify",

View File

@@ -88,36 +88,6 @@ export type Sounds = 'ASKED_TO_UNMUTE_SOUND' |
'RECORDING_ON_SOUND' |
'TALK_WHILE_MUTED_SOUND';
export interface IMobileDynamicLink {
apn: string;
appCode: string;
customDomain?: string;
ibi: string;
isi: string;
}
export interface IDeeplinkingPlatformConfig {
appName: string;
}
export interface IDeeplinkingMobileConfig extends IDeeplinkingPlatformConfig {
appPackage?: string;
appScheme: string;
downloadLink: string;
dynamicLink?: IMobileDynamicLink;
fDroidUrl?: string;
}
export interface IDeeplinkingConfig {
android?: IDeeplinkingMobileConfig;
desktop?: IDeeplinkingPlatformConfig;
disabled: boolean;
hideLogo: boolean;
ios?: IDeeplinkingMobileConfig;
showImage: boolean;
}
export interface IConfig {
_desktopSharingSourceDevice?: string;
analytics?: {
@@ -206,7 +176,6 @@ export interface IConfig {
};
};
corsAvatarURLs?: Array<string>;
deeplinking?: IDeeplinkingConfig;
defaultLanguage?: string;
defaultLocalDisplayName?: string;
defaultLogoUrl?: string;
@@ -233,6 +202,7 @@ export interface IConfig {
disableChatSmileys?: boolean;
disableDeepLinking?: boolean;
disableFilmstripAutohiding?: boolean;
disableH264?: boolean;
disableIncomingMessageSound?: boolean;
disableInitialGUM?: boolean;
disableInviteFunctions?: boolean;
@@ -413,10 +383,12 @@ export interface IConfig {
opusMaxAverageBitrate?: number;
p2p?: {
backToP2PDelay?: number;
disableH264?: boolean;
disabledCodec?: string;
enableUnifiedOnChrome?: boolean;
enabled?: boolean;
iceTransportPolicy?: string;
preferH264?: boolean;
preferredCodec?: string;
stunServers?: Array<{ urls: string; }>;
};
@@ -427,6 +399,7 @@ export interface IConfig {
};
pcStatsInterval?: number;
peopleSearchUrl?: string;
preferH264?: boolean;
preferredTranscribeLanguage?: string;
prejoinConfig?: {
enabled?: boolean;

View File

@@ -81,8 +81,6 @@ export default [
'brandingRoomAlias',
'debug',
'debugAudioLevels',
'deeplinking.disabled',
'deeplinking.showImage',
'defaultLocalDisplayName',
'defaultRemoteDisplayName',
'deploymentUrls',
@@ -101,6 +99,7 @@ export default [
'disabledSounds',
'disableFilmstripAutohiding',
'disableInitialGUM',
'disableH264',
'disableHPF',
'disableInviteFunctions',
'disableIncomingMessageSound',
@@ -198,6 +197,7 @@ export default [
'p2p',
'participantsPane',
'pcStatsInterval',
'preferH264',
'preferredCodec',
'prejoinConfig',
'prejoinPageEnabled',

View File

@@ -4,7 +4,7 @@ import { IReduxState } from '../../app/types';
import { REPLACE_PARTICIPANT } from '../flags/constants';
import { getFeatureFlag } from '../flags/functions';
import { IConfig, IDeeplinkingConfig } from './configType';
import { IConfig } from './configType';
export * from './functions.any';
@@ -15,18 +15,11 @@ export * from './functions.any';
* @returns {void}
*/
export function _cleanupConfig(config: IConfig) {
config.analytics = config.analytics ?? {};
config.analytics = {};
config.analytics.scriptURLs = [];
if (NativeModules.AppInfo.LIBRE_BUILD) {
delete config.analytics?.amplitudeAPPKey;
delete config.analytics?.googleAnalyticsTrackingId;
delete config.analytics?.rtcstatsEnabled;
delete config.analytics?.rtcstatsEndpoint;
delete config.analytics?.rtcstatsPollInterval;
delete config.analytics?.rtcstatsSendSdp;
delete config.analytics?.rtcstatsUseLegacy;
delete config.analytics?.obfuscateRoomName;
delete config.callStatsID;
delete config.callStatsSecret;
config.giphy = { enabled: false };
@@ -42,14 +35,3 @@ export function _cleanupConfig(config: IConfig) {
export function getReplaceParticipant(state: IReduxState): string {
return getFeatureFlag(state, REPLACE_PARTICIPANT, false);
}
/**
* Sets the defaults for deeplinking.
*
* @param {IDeeplinkingConfig} _deeplinking - The deeplinking config.
* @returns {void}
*/
export function _setDeeplinkingDefaults(_deeplinking: IDeeplinkingConfig) {
return;
}

View File

@@ -1,6 +1,6 @@
import { IReduxState } from '../../app/types';
import { IConfig, IDeeplinkingConfig, IDeeplinkingMobileConfig, IDeeplinkingPlatformConfig } from './configType';
import { IConfig } from './configType';
import { TOOLBAR_BUTTONS } from './constants';
export * from './functions.any';
@@ -8,11 +8,10 @@ export * from './functions.any';
/**
* Removes all analytics related options from the given configuration, in case of a libre build.
*
* @param {*} _config - The configuration which needs to be cleaned up.
* @param {*} config - The configuration which needs to be cleaned up.
* @returns {void}
*/
export function _cleanupConfig(_config: IConfig) {
return;
export function _cleanupConfig(config: IConfig) { // eslint-disable-line @typescript-eslint/no-unused-vars
}
/**
@@ -61,43 +60,3 @@ export function areAudioLevelsEnabled(state: IReduxState): boolean {
// Default to false for React Native as audio levels are of no interest to the mobile app.
return navigator.product !== 'ReactNative' && !state['features/base/config'].disableAudioLevels;
}
/**
* Sets the defaults for deeplinking.
*
* @param {IDeeplinkingConfig} deeplinking - The deeplinking config.
* @returns {void}
*/
export function _setDeeplinkingDefaults(deeplinking: IDeeplinkingConfig) {
const {
desktop = {} as IDeeplinkingPlatformConfig,
android = {} as IDeeplinkingMobileConfig,
ios = {} as IDeeplinkingMobileConfig
} = deeplinking;
desktop.appName = desktop.appName || 'Jitsi Meet';
ios.appName = ios.appName || 'Jitsi Meet';
ios.appScheme = ios.appScheme || 'org.jitsi.meet';
ios.downloadLink = ios.downloadLink
|| 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905';
if (ios.dynamicLink) {
ios.dynamicLink.apn = ios.dynamicLink.apn || 'org.jitsi.meet';
ios.dynamicLink.appCode = ios.dynamicLink.appCode || 'w2atb';
ios.dynamicLink.ibi = ios.dynamicLink.ibi || 'com.atlassian.JitsiMeet.ios';
ios.dynamicLink.isi = ios.dynamicLink.isi || '1165103905';
}
android.appName = android.appName || 'Jitsi Meet';
android.appScheme = android.appScheme || 'org.jitsi.meet';
android.downloadLink = android.downloadLink
|| 'https://play.google.com/store/apps/details?id=org.jitsi.meet';
android.appPackage = android.appPackage || 'org.jitsi.meet';
android.fDroidUrl = android.fDroidUrl || 'https://f-droid.org/en/packages/org.jitsi.meet/';
if (android.dynamicLink) {
android.dynamicLink.apn = android.dynamicLink.apn || 'org.jitsi.meet';
android.dynamicLink.appCode = android.dynamicLink.appCode || 'w2atb';
android.dynamicLink.ibi = android.dynamicLink.ibi || 'com.atlassian.JitsiMeet.ios';
android.dynamicLink.isi = android.dynamicLink.isi || '1165103905';
}
}

View File

@@ -2,6 +2,7 @@ import { AnyAction } from 'redux';
import { IStore } from '../../app/types';
import { getFeatureFlag } from '../flags/functions';
import Platform from '../react/Platform';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { updateSettings } from '../settings/actions';
@@ -52,9 +53,7 @@ function _setConfig({ dispatch, getState }: IStore, next: Function, action: AnyA
const settings = state['features/base/settings'];
const config: IConfig = {};
// FIXME: P2P is currently temporality disabled on mobile.
// eslint-disable-next-line no-constant-condition
if (false && typeof settings.disableP2P !== 'undefined') {
if (Platform.OS !== 'android' && typeof settings.disableP2P !== 'undefined') {
config.p2p = { enabled: !settings.disableP2P };
}

View File

@@ -1,6 +1,7 @@
import _ from 'lodash';
import { CONFERENCE_INFO } from '../../conference/components/constants';
import Platform from '../react/Platform';
import ReducerRegistry from '../redux/ReducerRegistry';
import { equals } from '../redux/functions';
@@ -11,14 +12,8 @@ import {
SET_CONFIG,
UPDATE_CONFIG
} from './actionTypes';
import {
IConfig,
IDeeplinkingConfig,
IDeeplinkingMobileConfig,
IDeeplinkingPlatformConfig,
IMobileDynamicLink
} from './configType';
import { _cleanupConfig, _setDeeplinkingDefaults } from './functions';
import { IConfig } from './configType';
import { _cleanupConfig } from './functions';
/**
* The initial state of the feature base/config when executing in a
@@ -53,18 +48,16 @@ const INITIAL_RN_STATE: IConfig = {
// fastest to merely disable them.
disableAudioLevels: true,
// FIXME: Mobile codecs should probably be configurable separately, rather
// than requiring this override here...
p2p: {
// Temporarily disable P2P on mobile while we sort out some (codec?) issues.
enabled: false,
disabledCodec: 'vp9',
// Temporarily disable P2P on Android while we sort out some (codec?) issues.
...(Platform.OS === 'android' ? { enabled: false } : {}), // eslint-disable-line no-extra-parens
preferredCodec: 'h264'
},
videoQuality: {
disabledCodec: 'vp9',
// FIXME: Mobile codecs should probably be configurable separately, rather
// than requiring this override here...
enforcePreferredCodec: true,
preferredCodec: 'vp8'
}
};
@@ -298,52 +291,6 @@ function _translateInterfaceConfig(oldValue: IConfig) {
}
}
// if we have `deeplinking` defined, ignore deprecated values. Otherwise, compose the config.
if (!oldValue.deeplinking) {
const disabled = Boolean(oldValue.disableDeepLinking);
const deeplinking: IDeeplinkingConfig = {
desktop: {} as IDeeplinkingPlatformConfig,
hideLogo: false,
disabled,
showImage: false,
android: {} as IDeeplinkingMobileConfig,
ios: {} as IDeeplinkingMobileConfig
};
if (typeof interfaceConfig === 'object') {
const mobileDynamicLink = interfaceConfig.MOBILE_DYNAMIC_LINK;
const dynamicLink: IMobileDynamicLink | undefined = mobileDynamicLink ? {
apn: mobileDynamicLink.APN,
appCode: mobileDynamicLink.APP_CODE,
ibi: mobileDynamicLink.IBI,
isi: mobileDynamicLink.ISI,
customDomain: mobileDynamicLink.CUSTOM_DOMAIN
} : undefined;
if (deeplinking.desktop) {
deeplinking.desktop.appName = interfaceConfig.NATIVE_APP_NAME;
}
deeplinking.hideLogo = Boolean(interfaceConfig.HIDE_DEEP_LINKING_LOGO);
deeplinking.showImage = interfaceConfig.SHOW_DEEP_LINKING_IMAGE;
deeplinking.android = {
appName: interfaceConfig.NATIVE_APP_NAME,
appScheme: interfaceConfig.APP_SCHEME,
downloadLink: interfaceConfig.MOBILE_DOWNLOAD_LINK_ANDROID,
appPackage: interfaceConfig.ANDROID_APP_PACKAGE,
fDroidUrl: interfaceConfig.MOBILE_DOWNLOAD_LINK_F_DROID,
dynamicLink
};
deeplinking.ios = {
appName: interfaceConfig.NATIVE_APP_NAME,
appScheme: interfaceConfig.APP_SCHEME,
downloadLink: interfaceConfig.MOBILE_DOWNLOAD_LINK_IOS,
dynamicLink
};
}
newValue.deeplinking = deeplinking;
}
return newValue;
}
@@ -532,8 +479,6 @@ function _translateLegacyConfig(oldValue: IConfig) {
};
}
_setDeeplinkingDefaults(newValue.deeplinking as IDeeplinkingConfig);
return newValue;
}

View File

@@ -5,6 +5,7 @@ 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_REMOTE_PARTICIPANTS_UPDATED,
SET_CAR_MODE,
SET_TILE_VIEW,
VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED
@@ -101,6 +102,7 @@ MiddlewareRegistry.register(store => next => action => {
case PARTICIPANT_JOINED:
case PARTICIPANT_KICKED:
case PARTICIPANT_LEFT:
case SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED:
case SELECT_LARGE_VIDEO_PARTICIPANT:
case SET_AUDIO_ONLY:
case SET_CAR_MODE:

View File

@@ -17,6 +17,7 @@ import {
} from './actionTypes';
import {
MEDIA_TYPE,
type MediaType,
SCREENSHARE_MUTISM_AUTHORITY,
VIDEO_MUTISM_AUTHORITY
} from './constants';
@@ -94,12 +95,14 @@ export function setCameraFacingMode(cameraFacingMode: string) {
* Action to set the muted state of the local screenshare.
*
* @param {boolean} muted - True if the local screenshare is to be enabled or false otherwise.
* @param {MEDIA_TYPE} mediaType - The type of media.
* @param {number} authority - The {@link SCREENSHARE_MUTISM_AUTHORITY} which is muting/unmuting the local screenshare.
* @param {boolean} ensureTrack - True if we want to ensure that a new track is created if missing.
* @returns {Function}
*/
export function setScreenshareMuted(
muted: boolean,
mediaType: MediaType = MEDIA_TYPE.SCREENSHARE,
authority: number = SCREENSHARE_MUTISM_AUTHORITY.USER,
ensureTrack = false) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
@@ -122,6 +125,7 @@ export function setScreenshareMuted(
return dispatch({
type: SET_SCREENSHARE_MUTED,
authority,
mediaType,
ensureTrack,
muted: newValue
});
@@ -150,6 +154,7 @@ export function setVideoAvailable(available: boolean) {
*
* @param {boolean} muted - True if the local video is to be muted or false if
* the local video is to be unmuted.
* @param {MEDIA_TYPE} mediaType - The type of media.
* @param {number} authority - The {@link VIDEO_MUTISM_AUTHORITY} which is
* muting/unmuting the local video.
* @param {boolean} ensureTrack - True if we want to ensure that a new track is
@@ -158,6 +163,7 @@ export function setVideoAvailable(available: boolean) {
*/
export function setVideoMuted(
muted: boolean,
mediaType: string = MEDIA_TYPE.VIDEO,
authority: number = VIDEO_MUTISM_AUTHORITY.USER,
ensureTrack = false) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
@@ -180,6 +186,7 @@ export function setVideoMuted(
return dispatch({
type: SET_VIDEO_MUTED,
authority,
mediaType,
ensureTrack,
muted: newValue
});

View File

@@ -166,7 +166,7 @@ function _appStateChanged({ dispatch, getState }, next, action) {
sendAnalytics(createTrackMutedEvent('video', 'background mode', mute));
dispatch(setVideoMuted(mute, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
dispatch(setVideoMuted(mute, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
}
return next(action);
@@ -191,9 +191,9 @@ function _setAudioOnly({ dispatch, getState }, next, action) {
sendAnalytics(createTrackMutedEvent('video', 'audio-only mode', audioOnly));
// Make sure we mute both the desktop and video tracks.
dispatch(setVideoMuted(audioOnly, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY));
dispatch(setVideoMuted(audioOnly, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY));
if (getMultipleVideoSendingSupportFeatureFlag(state)) {
dispatch(setScreenshareMuted(audioOnly, SCREENSHARE_MUTISM_AUTHORITY.AUDIO_ONLY));
dispatch(setScreenshareMuted(audioOnly, MEDIA_TYPE.SCREENSHARE, SCREENSHARE_MUTISM_AUTHORITY.AUDIO_ONLY));
}
return next(action);

View File

@@ -0,0 +1,29 @@
// @flow
import React from 'react';
import WebView from 'react-native-webview';
import JitsiScreen from './JitsiScreen';
type Props = {
/**
* The URL to display.
*/
source: string,
/**
* The component's external style.
*/
style: Object
}
const JitsiScreenWebView = ({ source, style }: Props) => (
<JitsiScreen
disableForcedKeyboardDismiss = { true }
style = { style }>
<WebView source = {{ uri: source }} />
</JitsiScreen>
);
export default JitsiScreenWebView;

View File

@@ -351,11 +351,11 @@ export function isWhiteboardParticipant(participant?: IParticipant): boolean {
* features/base/participants.
* @returns {number}
*/
export function getRemoteParticipantCountWithFake(stateful: IStateful) {
export function getRemoteParticipantCount(stateful: IStateful) {
const state = toState(stateful);
const participantsState = state['features/base/participants'];
return participantsState.remote.size;
return participantsState.remote.size - participantsState.sortedRemoteVirtualScreenshareParticipants.size;
}
/**

View File

@@ -1,3 +1,6 @@
import {
SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED
} from '../../video-layout/actionTypes';
import ReducerRegistry from '../redux/ReducerRegistry';
import { set } from '../redux/functions';
@@ -71,6 +74,7 @@ const DEFAULT_STATE = {
remote: new Map(),
sortedRemoteVirtualScreenshareParticipants: new Map(),
sortedRemoteParticipants: new Map(),
sortedRemoteScreenshares: new Map(),
speakersList: new Map()
};
@@ -85,6 +89,7 @@ export interface IParticipantsState {
raisedHandsQueue: Array<{ id: string; raisedHandTimestamp: number; }>;
remote: Map<string, IParticipant>;
sortedRemoteParticipants: Map<string, string>;
sortedRemoteScreenshares: Map<string, string>;
sortedRemoteVirtualScreenshareParticipants: Map<string, string>;
speakersList: Map<string, string>;
}
@@ -380,6 +385,29 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
raisedHandsQueue: action.queue
};
}
case SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED: {
const { participantIds } = action;
const sortedSharesList = [];
for (const participant of participantIds) {
const remoteParticipant = state.remote.get(participant);
if (remoteParticipant) {
const displayName
= _getDisplayName(state, remoteParticipant.name);
sortedSharesList.push([ participant, displayName ]);
}
}
// Keep the remote screen share list sorted alphabetically.
sortedSharesList.length && sortedSharesList.sort((a, b) => a[1].localeCompare(b[1]));
// @ts-ignore
state.sortedRemoteScreenshares = new Map(sortedSharesList);
return { ...state };
}
case OVERWRITE_PARTICIPANT_NAME: {
const { id, name } = action;

View File

@@ -38,11 +38,6 @@ type Props = {
*/
_showJitsiWatermark: boolean,
/**
* Whether the watermark should have a `top` and `left` value.
*/
noMargins: boolean;
/**
* The default value for the Jitsi logo URL.
*/
@@ -165,9 +160,7 @@ class Watermarks extends Component<Props, State> {
_logoUrl,
_showJitsiWatermark
} = this.props;
const { noMargins, t } = this.props;
const className = `watermark ${noMargins ? 'leftwatermarknomargin' : 'leftwatermark'}`;
const { t } = this.props;
let reactElement = null;
if (_showJitsiWatermark) {
@@ -179,14 +172,14 @@ class Watermarks extends Component<Props, State> {
};
reactElement = (<div
className = { className }
className = 'watermark leftwatermark'
style = { style } />);
if (_logoLink) {
reactElement = (
<a
aria-label = { t('jitsiHome', { logo: interfaceConfig.APP_NAME }) }
className = { className }
className = 'watermark leftwatermark'
href = { _logoLink }
target = '_new'>
{ reactElement }

View File

@@ -2,14 +2,16 @@ import { IReduxState, IStore } from '../../app/types';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import { setPictureInPictureEnabled } from '../../mobile/picture-in-picture/functions';
import { showNotification } from '../../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
import { setAudioOnly } from '../audio-only/actions';
import JitsiMeetJS from '../lib-jitsi-meet';
import {
setScreenshareMuted,
setVideoMuted
} from '../media/actions';
import { VIDEO_MUTISM_AUTHORITY } from '../media/constants';
import {
MEDIA_TYPE,
VIDEO_MUTISM_AUTHORITY
} from '../media/constants';
import { addLocalTrack, replaceLocalTrack } from './actions.any';
import { getLocalDesktopTrack, getTrackState, isLocalVideoTrackDesktop } from './functions.native';
@@ -38,7 +40,7 @@ export function toggleScreensharing(enabled: boolean, _ignore1?: boolean, _ignor
}
} else {
dispatch(setScreenshareMuted(true));
dispatch(setVideoMuted(false, VIDEO_MUTISM_AUTHORITY.SCREEN_SHARE));
dispatch(setVideoMuted(false, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.SCREEN_SHARE));
setPictureInPictureEnabled(true);
}
};
@@ -71,16 +73,12 @@ async function _startScreenSharing(dispatch: Function, state: IReduxState) {
dispatch(addLocalTrack(track));
}
dispatch(setVideoMuted(true, VIDEO_MUTISM_AUTHORITY.SCREEN_SHARE));
dispatch(setVideoMuted(true, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.SCREEN_SHARE));
const { enabled: audioOnly } = state['features/base/audio-only'];
if (audioOnly) {
dispatch(showNotification({
titleKey: 'notify.screenSharingAudioOnlyTitle',
descriptionKey: 'notify.screenSharingAudioOnlyDescription',
maxLines: 3
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
dispatch(setAudioOnly(false));
}
} catch (error: any) {
console.log('ERROR creating ScreeSharing stream ', error);

View File

@@ -14,6 +14,7 @@ import { isAudioOnlySharing, isScreenVideoShared } from '../../screen-share/func
import { isScreenshotCaptureEnabled, toggleScreenshotCaptureSummary } from '../../screenshot-capture';
// @ts-ignore
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
import { setAudioOnly } from '../audio-only/actions';
import { getCurrentConference } from '../conference/functions';
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
import { setScreenshareMuted } from '../media/actions';
@@ -226,15 +227,12 @@ async function _toggleScreenSharing(
}
}
// Show notification about more bandwidth usage in audio-only mode if the user starts screensharing. This
// doesn't apply to audio-only screensharing.
// Disable audio-only or best performance mode if the user starts screensharing. This doesn't apply to
// audio-only screensharing.
const { enabled: bestPerformanceMode } = state['features/base/audio-only'];
if (bestPerformanceMode && !audioOnly) {
dispatch(showNotification({
titleKey: 'notify.screenSharingAudioOnlyTitle',
descriptionKey: 'notify.screenSharingAudioOnlyDescription'
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
dispatch(setAudioOnly(false));
}
} else {
const { desktopAudioTrack } = state['features/screen-share'];

View File

@@ -79,7 +79,7 @@ MiddlewareRegistry.register(store => next => action => {
}
case SET_SCREENSHARE_MUTED:
_setMuted(store, action, MEDIA_TYPE.SCREENSHARE);
_setMuted(store, action, action.mediaType);
break;
case SET_VIDEO_MUTED:
@@ -88,7 +88,7 @@ MiddlewareRegistry.register(store => next => action => {
return;
}
_setMuted(store, action, MEDIA_TYPE.VIDEO);
_setMuted(store, action, action.mediaType);
break;
case TOGGLE_CAMERA_FACING_MODE: {

View File

@@ -1,13 +1,6 @@
import { parseURLParams } from './parseURLParams';
import { normalizeNFKC } from './strings';
/**
* Http status codes.
*/
export enum StatusCode {
PaymentRequired = 402
}
/**
* The app linking scheme.
* TODO: This should be read from the manifest files later.

View File

@@ -5,7 +5,6 @@ import React, { Component } from 'react';
import type { Dispatch } from 'redux';
import { createDeepLinkingPageEvent, sendAnalytics } from '../../analytics';
import { IDeeplinkingConfig } from '../../base/config/configType';
import { isSupportedBrowser } from '../../base/environment';
import { translate } from '../../base/i18n';
import { connect } from '../../base/redux';
@@ -17,17 +16,14 @@ import {
} from '../actions';
import { _TNS } from '../constants';
declare var interfaceConfig: Object;
/**
* The type of the React {@code Component} props of
* {@link DeepLinkingDesktopPage}.
*/
type Props = {
/**
* The deeplinking config.
*/
_deeplinkingCfg: IDeeplinkingConfig,
/**
* Used to dispatch actions from the buttons.
*/
@@ -76,10 +72,10 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
* @returns {ReactElement}
*/
render() {
const { t, _deeplinkingCfg: { desktop = {}, hideLogo, showImage } } = this.props;
const { appName } = desktop;
const { t } = this.props;
const { HIDE_DEEP_LINKING_LOGO, NATIVE_APP_NAME, SHOW_DEEP_LINKING_IMAGE } = interfaceConfig;
const rightColumnStyle
= showImage ? null : { width: '100%' };
= SHOW_DEEP_LINKING_IMAGE ? null : { width: '100%' };
return (
@@ -88,7 +84,7 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
<div className = 'deep-linking-desktop'>
<div className = 'header'>
{
hideLogo
HIDE_DEEP_LINKING_LOGO
? null
: <img
alt = { t('welcomepage.logo.logoDeepLinking') }
@@ -98,7 +94,7 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
</div>
<div className = 'content'>
{
showImage
SHOW_DEEP_LINKING_IMAGE
? <div className = 'leftColumn'>
<div className = 'leftColumnContent'>
<div className = 'image' />
@@ -112,7 +108,7 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
<h1 className = 'title'>
{
t(`${_TNS}.title`,
{ app: appName })
{ app: NATIVE_APP_NAME })
}
</h1>
<p className = 'description'>
@@ -121,7 +117,7 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
`${_TNS}.${isSupportedBrowser()
? 'description'
: 'descriptionWithoutWeb'}`,
{ app: appName }
{ app: NATIVE_APP_NAME }
)
}
</p>
@@ -175,18 +171,4 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
}
}
/**
* Maps (parts of) the Redux state to the associated props for the
* {@code DeepLinkingDesktopPage} component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
_deeplinkingCfg: state['features/base/config'].deeplinking || {}
};
}
export default translate(connect(_mapStateToProps)(DeepLinkingDesktopPage));
export default translate(connect()(DeepLinkingDesktopPage));

View File

@@ -4,7 +4,6 @@ import React, { Component } from 'react';
import type { Dispatch } from 'redux';
import { createDeepLinkingPageEvent, sendAnalytics } from '../../analytics';
import { IDeeplinkingConfig, IDeeplinkingMobileConfig } from '../../base/config/configType';
import { isSupportedMobileBrowser } from '../../base/environment';
import { translate } from '../../base/i18n';
import { Platform } from '../../base/react';
@@ -15,6 +14,8 @@ import { _TNS } from '../constants';
import { generateDeepLinkingURL } from '../functions';
import { renderPromotionalFooter } from '../renderPromotionalFooter';
declare var interfaceConfig: Object;
/**
* The namespace of the CSS styles of DeepLinkingMobilePage.
*
@@ -30,19 +31,9 @@ const _SNS = 'deep-linking-mobile';
type Props = {
/**
* The deeplinking config.
* Application download URL.
*/
_deeplinkingCfg: IDeeplinkingConfig,
/**
* Application mobile deeplinking config.
*/
_mobileConfig: IDeeplinkingMobileConfig,
/**
* The deeplinking url.
*/
_deepLinkingUrl: string,
_downloadUrl: ?string,
/**
* The name of the conference attempting to being joined.
@@ -104,19 +95,13 @@ class DeepLinkingMobilePage extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const {
_deeplinkingCfg: { hideLogo, showImage },
_mobileConfig: { downloadLink, appName },
_room,
t,
_url,
_deepLinkingUrl
} = this.props;
const { _downloadUrl, _room, t, _url } = this.props;
const { HIDE_DEEP_LINKING_LOGO, NATIVE_APP_NAME, SHOW_DEEP_LINKING_IMAGE } = interfaceConfig;
const downloadButtonClassName
= `${_SNS}__button ${_SNS}__button_primary`;
const onOpenLinkProperties = downloadLink
const onOpenLinkProperties = _downloadUrl
? {
// When opening a link to the download page, we want to let the
// OS itself handle intercepting and opening the appropriate
@@ -136,7 +121,7 @@ class DeepLinkingMobilePage extends Component<Props> {
<div className = { _SNS }>
<div className = 'header'>
{
hideLogo
HIDE_DEEP_LINKING_LOGO
? null
: <img
alt = { t('welcomepage.logo.logoDeepLinking') }
@@ -146,7 +131,7 @@ class DeepLinkingMobilePage extends Component<Props> {
</div>
<div className = { `${_SNS}__body` }>
{
showImage
SHOW_DEEP_LINKING_IMAGE
? <img
alt = { t('welcomepage.logo.logoDeepLinking') }
className = 'image'
@@ -154,7 +139,7 @@ class DeepLinkingMobilePage extends Component<Props> {
: null
}
<p className = { `${_SNS}__text` }>
{ t(`${_TNS}.appNotInstalled`, { app: appName }) }
{ t(`${_TNS}.appNotInstalled`, { app: NATIVE_APP_NAME }) }
</p>
<p className = { `${_SNS}__text` }>
{ t(`${_TNS}.ifHaveApp`) }
@@ -162,7 +147,7 @@ class DeepLinkingMobilePage extends Component<Props> {
<a
{ ...onOpenLinkProperties }
className = { `${_SNS}__href` }
href = { _deepLinkingUrl }
href = { generateDeepLinkingURL() }
onClick = { this._onOpenApp }
target = '_top'>
<button className = { `${_SNS}__button ${_SNS}__button_primary` }>
@@ -215,28 +200,32 @@ class DeepLinkingMobilePage extends Component<Props> {
* @returns {string} - The URL for downloading the app.
*/
_generateDownloadURL() {
const { _mobileConfig: { downloadLink, dynamicLink, appScheme } } = this.props;
const { _downloadUrl: url } = this.props;
if (downloadLink && typeof dynamicLink === 'undefined') {
return downloadLink;
if (url && typeof interfaceConfig.MOBILE_DYNAMIC_LINK === 'undefined') {
return url;
}
// For information about the properties of
// interfaceConfig.MOBILE_DYNAMIC_LINK check:
// https://firebase.google.com/docs/dynamic-links/create-manually
const {
apn,
appCode,
customDomain,
ibi,
isi
} = dynamicLink || {};
APN = 'org.jitsi.meet',
APP_CODE = 'w2atb',
CUSTOM_DOMAIN = undefined,
IBI = 'com.atlassian.JitsiMeet.ios',
ISI = '1165103905'
} = interfaceConfig.MOBILE_DYNAMIC_LINK || {};
const domain = customDomain ?? `https://${appCode}.app.goo.gl`;
const domain = CUSTOM_DOMAIN ?? `https://${APP_CODE}.app.goo.gl`;
const IUS = interfaceConfig.APP_SCHEME || 'org.jitsi.meet';
return `${domain}/?link=${
encodeURIComponent(window.location.href)}&apn=${
apn}&ibi=${
ibi}&isi=${
isi}&ius=${
appScheme}&efr=1`;
APN}&ibi=${
IBI}&isi=${
ISI}&ius=${
IUS}&efr=1`;
}
_onDownloadApp: () => void;
@@ -292,15 +281,11 @@ class DeepLinkingMobilePage extends Component<Props> {
*/
function _mapStateToProps(state) {
const { locationURL = {} } = state['features/base/connection'];
const { deeplinking } = state['features/base/config'];
const mobileConfig = deeplinking?.[Platform.OS] || {};
return {
_deeplinkingCfg: deeplinking || {},
_mobileConfig: mobileConfig,
_downloadUrl: interfaceConfig[`MOBILE_DOWNLOAD_LINK_${Platform.OS.toUpperCase()}`],
_room: decodeURIComponent(state['features/base/conference'].room),
_url: locationURL,
_deepLinkingUrl: generateDeepLinkingURL(state)
_url: locationURL
};
}

View File

@@ -3,28 +3,15 @@
import React, { Component } from 'react';
import { createDeepLinkingPageEvent, sendAnalytics } from '../../analytics';
import { IDeeplinkingConfig } from '../../base/config/configType';
import { connect } from '../../base/redux';
/**
* The type of the React {@code Component} props of
* {@link NoMobileApp}.
*/
type Props = {
/**
* The deeplinking config.
*/
_deeplinkingCfg: IDeeplinkingConfig,
};
declare var interfaceConfig: Object;
/**
* React component representing no mobile app page.
*
* @class NoMobileApp
*/
class NoMobileApp<P : Props> extends Component<P> {
export default class NoMobileApp extends Component<*> {
/**
* Implements the Component's componentDidMount method.
*
@@ -43,7 +30,6 @@ class NoMobileApp<P : Props> extends Component<P> {
*/
render() {
const ns = 'no-mobile-app';
const { desktop: { appName } } = this.props._deeplinkingCfg;
return (
<div className = { ns }>
@@ -51,26 +37,10 @@ class NoMobileApp<P : Props> extends Component<P> {
Video chat isn't available on mobile.
</h2>
<p className = { `${ns}__description` }>
Please use { appName } on desktop to
Please use { interfaceConfig.NATIVE_APP_NAME } on desktop to
join calls.
</p>
</div>
);
}
}
/**
* Maps (parts of) the Redux state to the associated props for the
* {@code NoMobileApp} component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
_deeplinkingCfg: state['features/base/config'].deeplinking || {}
};
}
export default connect(_mapStateToProps)(NoMobileApp);

View File

@@ -15,31 +15,27 @@ import { _openDesktopApp } from './openDesktopApp';
/**
* Generates a deep linking URL based on the current window URL.
*
* @param {Object} state - Object containing current redux state.
*
* @returns {string} - The generated URL.
*/
export function generateDeepLinkingURL(state) {
export function generateDeepLinkingURL() {
// If the user installed the app while this Component was displayed
// (e.g. the user clicked the Download the App button), then we would
// like to open the current URL in the mobile app. The only way to do it
// appears to be a link with an app-specific scheme, not a Universal
// Link.
const appScheme = interfaceConfig.APP_SCHEME || 'org.jitsi.meet';
const { href } = window.location;
const regex = new RegExp(URI_PROTOCOL_PATTERN, 'gi');
const mobileConfig = state['features/base/config'].deeplinking?.[Platform.OS] || {};
const { appScheme, appPackage } = mobileConfig;
// Android: use an intent link, custom schemes don't work in all browsers.
// https://developer.chrome.com/multidevice/android/intents
if (Platform.OS === 'android') {
// https://meet.jit.si/foo -> meet.jit.si/foo
const url = href.replace(regex, '').substr(2);
const pkg = interfaceConfig.ANDROID_APP_PACKAGE || 'org.jitsi.meet';
return `intent://${url}#Intent;scheme=${appScheme};package=${appPackage};end`;
return `intent://${url}#Intent;scheme=${appScheme};package=${pkg};end`;
}
// iOS: Replace the protocol part with the app scheme.
@@ -56,13 +52,12 @@ export function generateDeepLinkingURL(state) {
export function getDeepLinkingPage(state) {
const { room } = state['features/base/conference'];
const { launchInWeb } = state['features/deep-linking'];
const deeplinking = state['features/base/config'].deeplinking || {};
const { appScheme } = deeplinking?.[Platform.OS] || {};
const appScheme = typeof interfaceConfig !== 'undefined' && interfaceConfig.APP_SCHEME;
// Show only if we are about to join a conference.
if (launchInWeb
|| !room
|| state['features/base/config'].deeplinking?.disabled
|| state['features/base/config'].disableDeepLinking
|| (isVpaasMeeting(state) && (!appScheme || appScheme === 'com.8x8.meet'))) {
return Promise.resolve();
}

View File

@@ -305,20 +305,13 @@ class FaceLandmarksDetector {
private async sendDataToWorker(faceCenteringThreshold = 10): Promise<boolean> {
if (!this.imageCapture
|| !this.worker
|| !this.imageCapture) {
|| !this.imageCapture?.track
|| this.imageCapture?.track.readyState !== 'live') {
logger.log('Environment not ready! Could not send data to worker');
return false;
}
// if ImageCapture is polyfilled then it would not have the track,
// so there would be no point in checking for its readyState
if (this.imageCapture.track && this.imageCapture.track.readyState !== 'live') {
logger.log('Track not ready! Could not send data to worker');
return false;
}
let imageBitmap;
let image;

View File

@@ -3,7 +3,7 @@ import { pinParticipant } from '../base/participants/actions';
import {
getLocalParticipant,
getParticipantById,
getRemoteParticipantCountWithFake
getRemoteParticipantCount
} from '../base/participants/functions';
import { shouldHideSelfView } from '../base/settings/functions.web';
import { getMaxColumnCount } from '../video-layout/functions.web';
@@ -149,7 +149,7 @@ export function setVerticalViewDimensions() {
const disableSelfView = shouldHideSelfView(state);
const resizableFilmstrip = isFilmstripResizable(state);
const _verticalViewGrid = showGridInVerticalView(state);
const numberOfRemoteParticipants = getRemoteParticipantCountWithFake(state);
const numberOfRemoteParticipants = getRemoteParticipantCount(state);
const { localScreenShare } = state['features/base/participants'];
let gridView = {};
@@ -261,7 +261,7 @@ export function setHorizontalViewDimensions() {
= clientWidth - (disableSelfView ? 0 : thumbnails?.local?.width) - HORIZONTAL_FILMSTRIP_MARGIN;
const remoteVideosContainerHeight
= thumbnails?.local?.height + TILE_VERTICAL_MARGIN + STAGE_VIEW_THUMBNAIL_VERTICAL_BORDER + SCROLL_SIZE;
const numberOfRemoteParticipants = getRemoteParticipantCountWithFake(state);
const numberOfRemoteParticipants = getRemoteParticipantCount(state);
const hasScroll
= remoteVideosContainerHeight
< (thumbnails?.remote.width + TILE_HORIZONTAL_MARGIN) * numberOfRemoteParticipants;

View File

@@ -10,8 +10,8 @@ import { translate } from '../../../../base/i18n/functions';
import { JitsiRecordingConstants } from '../../../../base/lib-jitsi-meet';
import { connect } from '../../../../base/redux/functions';
import Dialog from '../../../../base/ui/components/web/Dialog';
import { StatusCode } from '../../../../base/util/uri';
import { isDynamicBrandingDataLoaded } from '../../../../dynamic-branding/functions.any';
import { isVpaasMeeting } from '../../../../jaas/functions';
import { getActiveSession } from '../../../../recording/functions';
// @ts-ignore
import { updateDialInNumbers } from '../../../actions';
@@ -80,9 +80,9 @@ interface IProps extends WithTranslation {
_inviteUrl: string;
/**
* Whether the dial in limit has been exceeded.
* Whether or not the current meeting belongs to a JaaS user.
*/
_isDialInOverLimit?: boolean;
_isVpaasMeeting: boolean;
/**
* The current known URL for a live stream in progress.
@@ -120,7 +120,7 @@ function AddPeopleDialog({
_inviteAppName,
_inviteContactsVisible,
_inviteUrl,
_isDialInOverLimit,
_isVpaasMeeting,
_liveStreamViewURL,
_phoneNumber,
t,
@@ -182,7 +182,7 @@ function AddPeopleDialog({
&& <DialInSection phoneNumber = { _phoneNumber } />
}
{
!_phoneNumber && _dialInVisible && _isDialInOverLimit && <DialInLimit />
!_phoneNumber && _dialInVisible && _isVpaasMeeting && <DialInLimit />
}
</div>
</Dialog>
@@ -207,7 +207,6 @@ function mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
const hideInviteContacts = iAmRecorder || (!addPeopleEnabled && !dialOutEnabled);
const dialIn = state['features/invite']; // @ts-ignore
const phoneNumber = dialIn?.numbers ? _getDefaultPhoneNumber(dialIn.numbers) : undefined;
const isDialInOverLimit = dialIn?.error?.status === StatusCode.PaymentRequired;
return {
_dialIn: dialIn,
@@ -223,7 +222,7 @@ function mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
_inviteAppName: inviteAppName,
_inviteContactsVisible: interfaceConfig.ENABLE_DIAL_OUT && !hideInviteContacts,
_inviteUrl: getInviteURL(state),
_isDialInOverLimit: isDialInOverLimit,
_isVpaasMeeting: isVpaasMeeting(state),
_liveStreamViewURL: currentLiveStreamingSession?.liveStreamViewURL,
_phoneNumber: phoneNumber
};

View File

@@ -14,11 +14,11 @@ const useStyles = makeStyles()(theme => {
padding: '8px 16px'
},
limitInfo: {
color: theme.palette.text.primary,
color: theme.palette.field01,
...withPixelLineHeight(theme.typography.bodyShortRegular)
},
link: {
color: `${theme.palette.text.primary} !important`,
color: theme.palette.field01,
fontWeight: 'bold',
textDecoration: 'underline'
}

View File

@@ -9,11 +9,7 @@ import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
import { getLocalParticipant, isLocalParticipantModerator } from '../base/participants/functions';
import { toState } from '../base/redux/functions';
import { parseURLParams } from '../base/util/parseURLParams';
import {
StatusCode,
appendURLParam,
parseURIString
} from '../base/util/uri';
import { appendURLParam, parseURIString } from '../base/util/uri';
import { isVpaasMeeting } from '../jaas/functions';
import { getActiveSession } from '../recording/functions';
@@ -21,8 +17,7 @@ import { getDialInConferenceID, getDialInNumbers } from './_utils';
import {
DIAL_IN_INFO_PAGE_PATH_NAME,
INVITE_TYPES,
SIP_ADDRESS_REGEX,
UPGRADE_OPTIONS_TEXT
SIP_ADDRESS_REGEX
} from './constants';
import logger from './logger';
@@ -519,7 +514,6 @@ export function getShareInfoText(state: IReduxState, inviteUrl: string, useHtml?
if (includeDialInfo) {
const { room } = parseURIString(inviteUrl);
let numbersPromise;
let hasPaymentError = false;
if (state['features/invite'].numbers
&& state['features/invite'].conferenceID) {
@@ -566,19 +560,9 @@ export function getShareInfoText(state: IReduxState, inviteUrl: string, useHtml?
i18next.t('info.dialInConferenceID')} ${
conferenceID}#\n\n`;
})
.catch(error => {
logger.error('Error fetching numbers or conferenceID', error);
hasPaymentError = error?.status === StatusCode.PaymentRequired;
})
.catch(error =>
logger.error('Error fetching numbers or conferenceID', error))
.then(defaultDialInNumber => {
if (hasPaymentError) {
infoText += `${
i18next.t('info.dialInNumber')} ${i18next.t('info.reachedLimit')} ${
i18next.t('info.upgradeOptions')} ${UPGRADE_OPTIONS_TEXT}`;
return infoText;
}
let dialInfoPageUrl = getDialInfoPageURL(state, room);
if (useHtml) {

View File

@@ -26,9 +26,7 @@ const DEFAULT_STATE = {
export interface IInviteState {
calleeInfoVisible?: boolean;
conferenceID?: string | number;
error?: {
status: number;
};
error?: Error;
initialCalleeInfo?: Object;
numbers?: string[];
numbersEnabled: boolean;
@@ -75,7 +73,6 @@ ReducerRegistry.register<IInviteState>('features/invite', (state = DEFAULT_STATE
return {
...state,
conferenceID: action.conferenceID,
error: undefined,
numbers: action.dialInNumbers,
sipUri: action.sipUri,
numbersEnabled: true,
@@ -91,7 +88,6 @@ ReducerRegistry.register<IInviteState>('features/invite', (state = DEFAULT_STATE
return {
...state,
conferenceID: action.conferenceID,
error: undefined,
numbers: action.dialInNumbers,
numbersEnabled,
numbersFetched: true

View File

@@ -3,10 +3,14 @@ import { createStackNavigator } from '@react-navigation/stack';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import HelpView from '../../../../../settings/components/native/HelpView';
import PrivacyView from '../../../../../settings/components/native/PrivacyView';
import SettingsView
from '../../../../../settings/components/native/SettingsView';
import TermsView from '../../../../../settings/components/native/TermsView';
import { screen } from '../../../routes';
import {
linkScreenOptions,
navigationContainerTheme,
settingsScreenOptions,
welcomeScreenOptions
@@ -57,6 +61,27 @@ const SettingsNavigationContainer = ({ isInWelcomePage }: Props) => {
}}>
{ SettingsScreen }
</SettingsStack.Screen>
<SettingsStack.Screen
component = { HelpView }
name = { screen.settings.links.help }
options = {{
...linkScreenOptions,
title: t('helpView.title')
}} />
<SettingsStack.Screen
component = { TermsView }
name = { screen.settings.links.terms }
options = {{
...linkScreenOptions,
title: t('termsView.title')
}} />
<SettingsStack.Screen
component = { PrivacyView }
name = { screen.settings.links.privacy }
options = {{
...linkScreenOptions,
title: t('privacyView.title')
}} />
</SettingsStack.Navigator>
</NavigationContainer>
);

View File

@@ -26,6 +26,14 @@ export const fullScreenOptions = {
headerShown: false
};
export const linkScreenOptions = {
gestureEnabled: true,
headerShown: true,
headerTitleStyle: {
color: BaseTheme.palette.text01
}
};
/**
* Navigation container theme.
*/

View File

@@ -1,11 +1,43 @@
import { registerGlobals } from 'react-native-webrtc';
import {
MediaStream,
MediaStreamTrack,
RTCIceCandidate,
RTCSessionDescription,
mediaDevices,
permissions
} from 'react-native-webrtc';
import RTCPeerConnection from './RTCPeerConnection';
registerGlobals();
(global => {
// Override with ours.
// TODO: consider dropping our override.
global.RTCPeerConnection = RTCPeerConnection;
if (typeof global.MediaStream === 'undefined') {
global.MediaStream = MediaStream;
}
if (typeof global.MediaStreamTrack === 'undefined') {
global.MediaStreamTrack = MediaStreamTrack;
}
if (typeof global.RTCIceCandidate === 'undefined') {
global.RTCIceCandidate = RTCIceCandidate;
}
if (typeof global.RTCPeerConnection === 'undefined') {
global.RTCPeerConnection = RTCPeerConnection;
}
if (typeof global.RTCPeerConnection === 'undefined') {
global.webkitRTCPeerConnection = RTCPeerConnection;
}
if (typeof global.RTCSessionDescription === 'undefined') {
global.RTCSessionDescription = RTCSessionDescription;
}
const navigator = global.navigator;
if (navigator) {
if (typeof navigator.mediaDevices === 'undefined') {
navigator.mediaDevices = mediaDevices;
}
if (typeof navigator.permissions === 'undefined') {
navigator.permissions = permissions;
}
}
})(global || window || this); // eslint-disable-line no-invalid-this

View File

@@ -0,0 +1,85 @@
/* eslint-disable lines-around-comment */
import React, { PureComponent } from 'react';
import { IReduxState } from '../../../app/types';
// @ts-ignore
import JitsiScreenWebView from '../../../base/modal/components/JitsiScreenWebView';
import { connect } from '../../../base/redux/functions';
// @ts-ignore
import { renderArrowBackButton }
// @ts-ignore
from '../../../mobile/navigation/components/welcome/functions';
// @ts-ignore
import styles from './styles';
const DEFAULT_HELP_CENTRE_URL = 'https://web-cdn.jitsi.net/faq/meet-faq.html';
interface IProps {
/**
* The URL to display in the Help Centre.
*/
_url: string;
/**
* Default prop for navigating between screen components(React Navigation).
*/
navigation: Object;
}
/**
* Implements a page that renders the help content for the app.
*/
class HelpView extends PureComponent<IProps> {
/**
* Implements React's {@link Component#componentDidMount()}. Invoked
* immediately after mounting occurs.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
const {
navigation
} = this.props;
// @ts-ignore
navigation.setOptions({
headerLeft: () =>
renderArrowBackButton(() =>
// @ts-ignore
navigation.goBack())
});
}
/**
* Implements {@code PureComponent#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<JitsiScreenWebView
source = { this.props._url }
style = { styles.screenContainer } />
);
}
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState) {
return {
_url: state['features/base/config'].helpCentreURL || DEFAULT_HELP_CENTRE_URL
};
}
export default connect(_mapStateToProps)(HelpView);

View File

@@ -0,0 +1,48 @@
/* eslint-disable lines-around-comment */
import React, { useEffect } from 'react';
// @ts-ignore
import JitsiScreenWebView from '../../../base/modal/components/JitsiScreenWebView';
// @ts-ignore
import { renderArrowBackButton }
// @ts-ignore
from '../../../mobile/navigation/components/welcome/functions';
// @ts-ignore
import styles from './styles';
interface IProps {
/**
* Default prop for navigating between screen components(React Navigation).
*/
navigation: Object;
}
/**
* The URL at which the privacy policy is available to the user.
*/
const PRIVACY_URL = 'https://jitsi.org/meet/privacy';
const PrivacyView = ({ navigation }: IProps) => {
useEffect(() => {
// @ts-ignore
navigation.setOptions({
headerLeft: () =>
renderArrowBackButton(() =>
// @ts-ignore
navigation.goBack())
});
});
return (
<JitsiScreenWebView
source = { PRIVACY_URL }
style = { styles.screenContainer } />
);
};
export default PrivacyView;

View File

@@ -1,11 +1,11 @@
/* eslint-disable lines-around-comment */
import { Link } from '@react-navigation/native';
import _ from 'lodash';
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import {
Alert,
Linking,
NativeModules,
Platform,
ScrollView,
@@ -14,7 +14,6 @@ import {
} from 'react-native';
import { Divider } from 'react-native-paper';
import { getDefaultURL } from '../../../app/functions.native';
import { IReduxState } from '../../../app/types';
// @ts-ignore
@@ -25,11 +24,10 @@ import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { getLocalParticipant } from '../../../base/participants/functions';
import { connect } from '../../../base/redux/functions';
import { updateSettings } from '../../../base/settings/actions';
import Button from '../../../base/ui/components/native/Button';
import Input from '../../../base/ui/components/native/Input';
import Switch from '../../../base/ui/components/native/Switch';
// @ts-ignore
import { BUTTON_TYPES } from '../../../base/ui/constants.any';
import { screen } from '../../../mobile/navigation/routes';
// @ts-ignore
import { AVATAR_SIZE } from '../../../welcome/components/styles';
import { isServerURLChangeEnabled, normalizeUserInputURL } from '../../functions.native';
@@ -46,18 +44,6 @@ import styles from './styles';
*/
const { AppInfo } = NativeModules;
/**
* The URL at which the terms (of service/use) are available to the user.
*/
const TERMS_URL = 'https://jitsi.org/meet/terms';
/**
* The URL at which the privacy policy is available to the user.
*/
const PRIVACY_URL = 'https://jitsi.org/meet/privacy';
const DEFAULT_HELP_CENTRE_URL = 'https://web-cdn.jitsi.net/faq/meet-faq.html';
interface IState {
@@ -118,13 +104,6 @@ interface IState {
*/
interface IProps extends WithTranslation {
/**
* The URL for when the help link.
*
* @protected
*/
_helpCentreUrl: string;
/**
* The ID of the local participant.
*/
@@ -246,9 +225,6 @@ class SettingsView extends Component<IProps, IState> {
this._onStartVideoMutedChange
= this._onStartVideoMutedChange.bind(this);
this._setURLFieldReference = this._setURLFieldReference.bind(this);
this._onShowHelpPressed = this._onShowHelpPressed.bind(this);
this._onShowPrivacyPressed = this._onShowPrivacyPressed.bind(this);
this._onShowTermsPressed = this._onShowTermsPressed.bind(this);
this._showURLAlert = this._showURLAlert.bind(this);
}
@@ -373,23 +349,26 @@ class SettingsView extends Component<IProps, IState> {
</FormSectionAccordion>
<FormSectionAccordion
label = 'settingsView.links'>
<Button
accessibilityLabel = 'settingsView.help'
labelKey = 'settingsView.help'
onClick = { this._onShowHelpPressed }
type = { BUTTON_TYPES.TERTIARY } />
<Link
style = { styles.sectionLink }
// @ts-ignore
to = {{ screen: screen.settings.links.help }}>
{ t('settingsView.help') }
</Link>
<Divider style = { styles.fieldSeparator } />
<Button
accessibilityLabel = 'settingsView.terms'
labelKey = 'settingsView.terms'
onClick = { this._onShowTermsPressed }
type = { BUTTON_TYPES.TERTIARY } />
<Link
style = { styles.sectionLink }
// @ts-ignore
to = {{ screen: screen.settings.links.terms }}>
{ t('settingsView.terms') }
</Link>
<Divider style = { styles.fieldSeparator } />
<Button
accessibilityLabel = 'settingsView.privacy'
labelKey = 'settingsView.privacy'
onClick = { this._onShowPrivacyPressed }
type = { BUTTON_TYPES.TERTIARY } />
<Link
style = { styles.sectionLink }
// @ts-ignore
to = {{ screen: screen.settings.links.privacy }}>
{ t('settingsView.privacy') }
</Link>
</FormSectionAccordion>
<FormSectionAccordion
label = 'settingsView.buildInfoSection'>
@@ -689,33 +668,6 @@ class SettingsView extends Component<IProps, IState> {
);
}
/**
* Opens the help url into the browser.
*
* @returns {void}
*/
_onShowHelpPressed() {
Linking.openURL(this.props._helpCentreUrl);
}
/**
* Opens the privacy url into the browser.
*
* @returns {void}
*/
_onShowPrivacyPressed() {
Linking.openURL(PRIVACY_URL);
}
/**
* Opens the terms url into the browser.
*
* @returns {void}
*/
_onShowTermsPressed() {
Linking.openURL(TERMS_URL);
}
/**
* Shows an alert warning the user about disabling crash reporting.
*
@@ -780,7 +732,6 @@ function _mapStateToProps(state: IReduxState) {
const localParticipant = getLocalParticipant(state);
return {
_helpCentreUrl: state['features/base/config'].helpCentreURL || DEFAULT_HELP_CENTRE_URL,
_localParticipantId: localParticipant?.id,
_serverURL: getDefaultURL(state),
_serverURLChangeEnabled: isServerURLChangeEnabled(state),

View File

@@ -0,0 +1,48 @@
/* eslint-disable lines-around-comment */
import React, { useEffect } from 'react';
// @ts-ignore
import JitsiScreenWebView from '../../../base/modal/components/JitsiScreenWebView';
// @ts-ignore
import { renderArrowBackButton }
// @ts-ignore
from '../../../mobile/navigation/components/welcome/functions';
// @ts-ignore
import styles from './styles';
interface IProps {
/**
* Default prop for navigating between screen components(React Navigation).
*/
navigation: Object;
}
/**
* The URL at which the terms (of service/use) are available to the user.
*/
const TERMS_URL = 'https://jitsi.org/meet/terms';
const TermsView = ({ navigation }: IProps) => {
useEffect(() => {
// @ts-ignore
navigation.setOptions({
headerLeft: () =>
renderArrowBackButton(() =>
// @ts-ignore
navigation.goBack())
});
});
return (
<JitsiScreenWebView
source = { TERMS_URL }
style = { styles.screenContainer } />
);
};
export default TermsView;

View File

@@ -109,6 +109,23 @@ export default {
fontSize: 14
},
sectionLink: {
...BaseTheme.typography.bodyShortBoldLarge,
color: BaseTheme.palette.link01,
margin: BaseTheme.spacing[3],
textAlign: 'center'
},
sectionLinkContainer: {
margin: BaseTheme.spacing[3]
},
sectionLinkText: {
...BaseTheme.typography.bodyShortBoldLarge,
color: BaseTheme.palette.link01,
textAlign: 'center'
},
/**
* Global {@code Text} color for the components.
*/

View File

@@ -5,7 +5,7 @@ import { sendAnalytics } from '../analytics/functions';
import { IStore } from '../app/types';
import { setAudioOnly } from '../base/audio-only/actions';
import { setVideoMuted } from '../base/media/actions';
import { VIDEO_MUTISM_AUTHORITY } from '../base/media/constants';
import { MEDIA_TYPE, VIDEO_MUTISM_AUTHORITY } from '../base/media/constants';
import {
SET_TOOLBOX_ENABLED,
@@ -96,6 +96,7 @@ export function handleToggleVideoMuted(muted: boolean, showUI: boolean, ensureTr
dispatch(
setVideoMuted(
muted,
MEDIA_TYPE.VIDEO,
VIDEO_MUTISM_AUTHORITY.USER,
ensureTrack));

View File

@@ -1,3 +1,15 @@
/**
* The type of the action which sets the list of known remote participant IDs which
* have an active screen share.
*
* @returns {{
* type: SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
* participantIds: Array<string>
* }}
*/
export const SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED
= 'SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED';
/**
* The type of the action which tells whether we are in carmode.
*

View File

@@ -1,11 +1,30 @@
import { IStore } from '../app/types';
import {
SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
SET_TILE_VIEW,
VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED
} from './actionTypes';
import { shouldDisplayTileView } from './functions';
/**
* Creates a (redux) action which signals that the list of known remote participants
* with screen shares has changed.
*
* @param {string} participantIds - The remote participants which currently have active
* screen share streams.
* @returns {{
* type: SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
* participantId: string
* }}
*/
export function setRemoteParticipantsWithScreenShare(participantIds: Array<string>) {
return {
type: SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
participantIds
};
}
/**
* Creates a (redux) action which signals that the list of known remote virtual screen share participant ids has
* changed.

View File

@@ -1,16 +1,18 @@
import { IStore } from '../app/types';
import { getCurrentConference } from '../base/conference/functions';
import { VIDEO_TYPE } from '../base/media/constants';
import { PARTICIPANT_LEFT, PIN_PARTICIPANT } from '../base/participants/actionTypes';
import { pinParticipant } from '../base/participants/actions';
import { getParticipantById, getPinnedParticipant } from '../base/participants/functions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { TRACK_REMOVED } from '../base/tracks/actionTypes';
import { SET_DOCUMENT_EDITING_STATUS } from '../etherpad/actionTypes';
import { isStageFilmstripEnabled } from '../filmstrip/functions';
import { isFollowMeActive } from '../follow-me/functions';
import { SET_TILE_VIEW } from './actionTypes';
import { setTileView } from './actions';
import { setRemoteParticipantsWithScreenShare, setTileView } from './actions';
import { getAutoPinSetting, updateAutoPinnedParticipant } from './functions';
import './subscriber';
@@ -72,6 +74,31 @@ MiddlewareRegistry.register(store => next => action => {
}
break;
}
// Update the remoteScreenShares.
// Because of the debounce in the subscriber which updates the remoteScreenShares we need to handle
// removal of screen shares separately here. Otherwise it is possible to have screen sharing
// participant that has already left in the remoteScreenShares array. This can lead to rendering
// a thumbnails for already left participants since the remoteScreenShares array is used for
// building the ordered list of remote participants.
case TRACK_REMOVED: {
const { jitsiTrack } = action.track;
if (jitsiTrack?.isVideoTrack() && jitsiTrack?.getVideoType() === VIDEO_TYPE.DESKTOP) {
const participantId = jitsiTrack.getParticipantId();
const oldScreenShares = store.getState()['features/video-layout'].remoteScreenShares || [];
const newScreenShares = oldScreenShares.filter(id => id !== participantId);
if (oldScreenShares.length !== newScreenShares.length) { // the participant was removed
store.dispatch(setRemoteParticipantsWithScreenShare(newScreenShares));
updateAutoPinnedParticipant(oldScreenShares, store);
}
}
break;
}
}
if (shouldUpdateAutoPin) {

View File

@@ -1,5 +1,5 @@
import { setVideoMuted } from '../base/media/actions';
import { VIDEO_MUTISM_AUTHORITY } from '../base/media/constants';
import { MEDIA_TYPE, VIDEO_MUTISM_AUTHORITY } from '../base/media/constants';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { CLIENT_RESIZED } from '../base/responsive-ui/actionTypes';
import { setLargeVideoDimensions } from '../large-video/actions.any';
@@ -19,7 +19,7 @@ MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case SET_CAR_MODE:
dispatch(setVideoMuted(action.enabled, VIDEO_MUTISM_AUTHORITY.CAR_MODE));
dispatch(setVideoMuted(action.enabled, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.CAR_MODE));
break;
case CLIENT_RESIZED: {
const { clientHeight, clientWidth } = store.getState()['features/base/responsive-ui'];

View File

@@ -1,6 +1,7 @@
import ReducerRegistry from '../base/redux/ReducerRegistry';
import {
SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
SET_CAR_MODE,
SET_TILE_VIEW,
VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED
@@ -40,6 +41,7 @@ const STORE_NAME = 'features/video-layout';
ReducerRegistry.register<IVideoLayoutState>(STORE_NAME, (state = DEFAULT_STATE, action): IVideoLayoutState => {
switch (action.type) {
case SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED:
case VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED:
return {
...state,

View File

@@ -52,7 +52,7 @@ export function muteLocal(enable: boolean, mediaType: MediaType, stopScreenShari
sendAnalytics(createToolbarEvent(isAudio ? AUDIO_MUTE : VIDEO_MUTE, { enable }));
dispatch(isAudio ? setAudioMuted(enable, /* ensureTrack */ true)
: setVideoMuted(enable, VIDEO_MUTISM_AUTHORITY.USER, /* ensureTrack */ true));
: setVideoMuted(enable, mediaType, VIDEO_MUTISM_AUTHORITY.USER, /* ensureTrack */ true));
// FIXME: The old conference logic still relies on this event being emitted.
typeof APP === 'undefined'

View File

@@ -6,7 +6,6 @@ import type { Dispatch } from 'redux';
import { createWelcomePageEvent, sendAnalytics } from '../../analytics';
import { appNavigate } from '../../app/actions';
import { IDeeplinkingConfig } from '../../base/config/configType';
import isInsecureRoomName from '../../base/util/isInsecureRoomName';
import { isCalendarEnabled } from '../../calendar-sync';
import { isRecentListEnabled } from '../../recent-list/functions';
@@ -21,11 +20,6 @@ export type Props = {
*/
_calendarEnabled: boolean,
/**
* The deeplinking config.
*/
_deeplinkingCfg: IDeeplinkingConfig,
/**
* Whether the insecure room name functionality is enabled or not.
*/
@@ -274,7 +268,6 @@ export class AbstractWelcomePage<P: Props> extends Component<P, *> {
export function _mapStateToProps(state: Object) {
return {
_calendarEnabled: isCalendarEnabled(state),
_deeplinkingCfg: state['features/base/config'].deeplinking || {},
_enableInsecureRoomNameWarning: state['features/base/config'].enableInsecureRoomNameWarning || false,
_moderatedRoomServiceUrl: state['features/base/config'].moderatedRoomServiceUrl,
_recentListEnabled: isRecentListEnabled(),

View File

@@ -184,27 +184,24 @@ class WelcomePage extends AbstractWelcomePage {
<div
className = { `welcome ${contentClassName} ${footerClassName}` }
id = 'welcome_page'>
<div className = 'welcome-watermark'>
<Watermarks defaultJitsiLogoURL = { DEFAULT_WELCOME_PAGE_LOGO_URL } />
</div>
<div className = 'header'>
<div className = 'welcome-page-settings'>
<SettingsButton
defaultTab = { SETTINGS_TABS.CALENDAR }
isDisplayedOnWelcomePage = { true } />
{ showAdditionalToolbarContent
? <div
className = 'settings-toolbar-content'
ref = { this._setAdditionalToolbarContentRef } />
: null
}
</div>
<div className = 'header-image' />
<div className = 'header-container'>
<div className = 'header-watermark-container'>
<div className = 'welcome-watermark'>
<Watermarks
defaultJitsiLogoURL = { DEFAULT_WELCOME_PAGE_LOGO_URL }
noMargins = { true } />
</div>
</div>
<div className = 'welcome-page-settings'>
<SettingsButton
defaultTab = { SETTINGS_TABS.CALENDAR }
isDisplayedOnWelcomePage = { true } />
{ showAdditionalToolbarContent
? <div
className = 'settings-toolbar-content'
ref = { this._setAdditionalToolbarContentRef } />
: null
}
</div>
<h1 className = 'header-text-title'>
{ t('welcomepage.headerTitle') }
</h1>
@@ -249,16 +246,18 @@ class WelcomePage extends AbstractWelcomePage {
{ _moderatedRoomServiceUrl && (
<div id = 'moderated-meetings'>
{
translateToHTML(
t, 'welcomepage.moderatedMessage', { url: _moderatedRoomServiceUrl })
}
<p>
{
translateToHTML(
t, 'welcomepage.moderatedMessage', { url: _moderatedRoomServiceUrl })
}
</p>
</div>)}
</div>
</div>
<div className = 'welcome-cards-container'>
<div className = 'welcome-card-column'>
<div className = 'welcome-card-row'>
<div className = 'welcome-tabs welcome-card welcome-card--blue'>
{ this._renderTabs() }
</div>
@@ -344,17 +343,12 @@ class WelcomePage extends AbstractWelcomePage {
* @returns {ReactElement}
*/
_renderFooter() {
const { t } = this.props;
const {
t,
_deeplinkingCfg: {
ios = {},
android = {}
}
} = this.props;
const { downloadLink: iosDownloadLink } = ios;
const { fDroidUrl, downloadLink: androidDownloadLink } = android;
MOBILE_DOWNLOAD_LINK_ANDROID,
MOBILE_DOWNLOAD_LINK_F_DROID,
MOBILE_DOWNLOAD_LINK_IOS
} = interfaceConfig;
return (<footer className = 'welcome-footer'>
<div className = 'welcome-footer-centered'>
@@ -363,21 +357,21 @@ class WelcomePage extends AbstractWelcomePage {
<div className = 'welcome-footer-row-1-text'>{t('welcomepage.jitsiOnMobile')}</div>
<a
className = 'welcome-badge'
href = { iosDownloadLink }>
href = { MOBILE_DOWNLOAD_LINK_IOS }>
<img
alt = { t('welcomepage.mobileDownLoadLinkIos') }
src = './images/app-store-badge.png' />
</a>
<a
className = 'welcome-badge'
href = { androidDownloadLink }>
href = { MOBILE_DOWNLOAD_LINK_ANDROID }>
<img
alt = { t('welcomepage.mobileDownLoadLinkAndroid') }
src = './images/google-play-badge.png' />
</a>
<a
className = 'welcome-badge'
href = { fDroidUrl }>
href = { MOBILE_DOWNLOAD_LINK_F_DROID }>
<img
alt = { t('welcomepage.mobileDownLoadLinkFDroid') }
src = './images/f-droid-badge.png' />
@@ -405,14 +399,14 @@ class WelcomePage extends AbstractWelcomePage {
if (_calendarEnabled) {
tabs.push({
label: t('welcomepage.upcomingMeetings'),
label: t('welcomepage.calendar'),
content: <CalendarList />
});
}
if (_recentListEnabled) {
tabs.push({
label: t('welcomepage.recentMeetings'),
label: t('welcomepage.recentList'),
content: <RecentList />
});
}

View File

@@ -5,6 +5,7 @@ local log = module._log;
local host = module.host;
local st = require "util.stanza";
local um_is_admin = require "core.usermanager".is_admin;
local jid_split = require "util.jid".split;
local function is_admin(jid)
@@ -39,8 +40,11 @@ log("debug",
-- option to disable room modification (sending muc config form) for guest that do not provide token
local require_token_for_moderation;
-- option to not require tokens for certain users and domains
local token_empty_allow_list;
local function load_config()
require_token_for_moderation = module:get_option_boolean("token_verification_require_token_for_moderation");
token_empty_allow_list = module:get_option_set("token_verification_empty_allow_list");
end
load_config();
@@ -57,6 +61,19 @@ local function verify_user(session, stanza)
return true;
end
-- if token is empty and user matches allow list, skip verification and allow user to join
local user, domain, res = jid_split(user_jid);
if session.auth_token == nil and user ~= nil and domain ~= nil and token_empty_allow_list then
if token_empty_allow_list:contains(domain) then
log("debug", "Token not required from user: %s in allowed domain: %s", user_jid, domain);
return true;
end
if token_empty_allow_list:contains(user..'@'..domain) then
log("debug", "Token not required from user in allowed list: %s", user_jid);
return true;
end
end
log("debug",
"Will verify token for user: %s, room: %s ", user_jid, stanza.attr.to);
if not token_util:verify_room(session, stanza.attr.to) then