Compare commits

..

1 Commits

Author SHA1 Message Date
Hristo Terezov
9235000a7f ref(remote-control): Use React/Redux. 2020-12-11 14:24:33 -06:00
75 changed files with 1009 additions and 1513 deletions

1
.gitignore vendored
View File

@@ -69,7 +69,6 @@ buck-out/
*.framework
android/app/debug
android/app/release
ios/sdk/out
# precommit-hook
.jshintignore

View File

@@ -25,5 +25,5 @@ android.enableDexingArtifactTransform.desugaring=false
android.useAndroidX=true
android.enableJetifier=true
appVersion=20.6.0
sdkVersion=2.12.0
appVersion=20.5.0
sdkVersion=2.11.0

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.jitsi.meet.sdk">
<!-- XXX ACCESS_NETWORK_STATE is required by WebRTC. -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -35,7 +34,7 @@
android:launchMode="singleTask"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:windowSoftInputMode="adjustResize"/>
android:windowSoftInputMode="adjustResize"></activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<service
@@ -49,13 +48,6 @@
<service
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
android:foregroundServiceType="mediaProjection" />
<provider
android:name="com.reactnativecommunity.webview.RNCWebViewFileProvider"
android:authorities="${applicationId}.fileprovider"
android:enabled="false"
tools:replace="android:authorities">
</provider>
</application>
</manifest>

View File

@@ -16,11 +16,8 @@
package org.jitsi.meet.sdk;
import android.media.AudioAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Build;
import java.util.HashSet;
import java.util.Set;
@@ -63,7 +60,7 @@ class AudioDeviceHandlerGeneric implements
private AudioManager audioManager;
/**
* {@link Runnable} for running audio device detection in the audio thread.
* {@link Runnable} for running audio device detection the main thread.
* This is only used on Android >= M.
*/
private final Runnable onAudioDeviceChangeRunner = new Runnable() {
@@ -145,7 +142,7 @@ class AudioDeviceHandlerGeneric implements
// Some other application potentially stole our audio focus
// temporarily. Restore our mode.
if (audioFocusLost) {
module.resetAudioRoute();
module.updateAudioRoute();
}
audioFocusLost = false;
break;
@@ -219,24 +216,8 @@ class AudioDeviceHandlerGeneric implements
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false);
int gotFocus;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
gotFocus = audioManager.requestAudioFocus(new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(this)
.build()
);
} else {
gotFocus = audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN);
}
if (gotFocus == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
if (audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN)
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
JitsiMeetLogger.w(TAG + " Audio focus request failed");
return false;
}

View File

@@ -16,7 +16,6 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
@@ -257,7 +256,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
if (mode != -1) {
JitsiMeetLogger.i(TAG + " User selected device set to: " + device);
userSelectedDevice = device;
updateAudioRoute(mode, false);
updateAudioRoute(mode);
}
}
});
@@ -277,22 +276,13 @@ class AudioModeModule extends ReactContextBaseJavaModule {
return;
}
Activity currentActivity = getCurrentActivity();
if (currentActivity != null) {
if (mode == DEFAULT) {
currentActivity.setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE);
} else {
currentActivity.setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
}
}
runInAudioThread(new Runnable() {
@Override
public void run() {
boolean success;
try {
success = updateAudioRoute(mode, false);
success = updateAudioRoute(mode);
} catch (Throwable e) {
success = false;
JitsiMeetLogger.e(e, TAG + " Failed to update audio route for mode: " + mode);
@@ -331,7 +321,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
* @return {@code true} if the audio route was updated successfully;
* {@code false}, otherwise.
*/
private boolean updateAudioRoute(int mode, boolean force) {
private boolean updateAudioRoute(int mode) {
JitsiMeetLogger.i(TAG + " Update audio route for mode: " + mode);
if (!audioDeviceHandler.setMode(mode)) {
@@ -366,7 +356,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
// If the previously selected device and the current default one
// match, do nothing.
if (!force && selectedDevice != null && selectedDevice.equals(audioDevice)) {
if (selectedDevice != null && selectedDevice.equals(audioDevice)) {
return true;
}
@@ -431,16 +421,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
*/
void updateAudioRoute() {
if (mode != -1) {
updateAudioRoute(mode, false);
}
}
/**
* Re-sets the current audio route. Needed when focus is lost and regained.
*/
void resetAudioRoute() {
if (mode != -1) {
updateAudioRoute(mode, true);
updateAudioRoute(mode);
}
}

View File

@@ -594,42 +594,6 @@ export default {
};
},
/**
* Displays error notifications according to the state carried by {@code errors} object returned
* by {@link createInitialLocalTracks}.
* @param {Object} errors - the errors (if any) returned by {@link createInitialLocalTracks}.
*
* @returns {void}
* @private
*/
_displayErrorsForCreateInitialLocalTracks(errors) {
const {
audioAndVideoError,
audioOnlyError,
screenSharingError,
videoOnlyError
} = errors;
// FIXME If there will be microphone error it will cover any screensharing dialog, but it's still better than in
// the reverse order where the screensharing dialog will sometimes be closing the microphone alert
// ($.prompt.close(); is called). Need to figure out dialogs chaining to fix that.
if (screenSharingError) {
this._handleScreenSharingError(screenSharingError);
}
if (audioAndVideoError || audioOnlyError) {
if (audioOnlyError || videoOnlyError) {
// If both requests for 'audio' + 'video' and 'audio' only failed, we assume that there are some
// problems with user's microphone and show corresponding dialog.
APP.store.dispatch(notifyMicError(audioOnlyError));
APP.store.dispatch(notifyCameraError(videoOnlyError));
} else {
// If request for 'audio' + 'video' failed, but request for 'audio' only was OK, we assume that we had
// problems with camera and show corresponding dialog.
APP.store.dispatch(notifyCameraError(audioAndVideoError));
}
}
},
/**
* Creates local media tracks and connects to a room. Will show error
* dialogs in case accessing the local microphone and/or camera failed. Will
@@ -650,11 +614,38 @@ export default {
*/
createInitialLocalTracksAndConnect(roomName, options = {}) {
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(options);
const {
audioAndVideoError,
audioOnlyError,
screenSharingError,
videoOnlyError
} = errors;
return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
.then(([ tracks, con ]) => {
this._displayErrorsForCreateInitialLocalTracks(errors);
// FIXME If there will be microphone error it will cover any
// screensharing dialog, but it's still better than in
// the reverse order where the screensharing dialog will
// sometimes be closing the microphone alert ($.prompt.close();
// is called). Need to figure out dialogs chaining to fix that.
if (screenSharingError) {
this._handleScreenSharingError(screenSharingError);
}
if (audioAndVideoError || audioOnlyError) {
if (audioOnlyError || videoOnlyError) {
// If both requests for 'audio' + 'video' and 'audio'
// only failed, we assume that there are some problems
// with user's microphone and show corresponding dialog.
APP.store.dispatch(notifyMicError(audioOnlyError));
APP.store.dispatch(notifyCameraError(videoOnlyError));
} else {
// If request for 'audio' + 'video' failed, but request
// for 'audio' only was OK, we assume that we had
// problems with camera and show corresponding dialog.
APP.store.dispatch(
notifyCameraError(audioAndVideoError));
}
}
return [ tracks, con ];
});
@@ -762,15 +753,7 @@ export default {
// they may remain as empty strings.
this._initDeviceList(true);
if (isPrejoinPageVisible(APP.store.getState())) {
return APP.store.dispatch(initPrejoin(tracks, errors));
}
logger.debug('Prejoin screen no longer displayed at the time when tracks were created');
this._displayErrorsForCreateInitialLocalTracks(errors);
return this._setLocalAudioVideoStreams(tracks);
return APP.store.dispatch(initPrejoin(tracks, errors));
}
const [ tracks, con ] = await this.createInitialLocalTracksAndConnect(

View File

@@ -14,6 +14,9 @@ var config = {
// Domain for authenticated users. Defaults to <domain>.
// authdomain: 'jitsi-meet.example.com',
// Call control component (Jigasi).
// call_control: 'callcontrol.jitsi-meet.example.com',
// Focus component domain. Defaults to focus.<domain>.
// focus: 'focus.jitsi-meet.example.com',
@@ -91,11 +94,6 @@ var config = {
// input and will suggest another valid device if one is present.
enableNoAudioDetection: true,
// Enabling this will show a "Save Logs" link in the GSM popover that can be
// used to collect debug information (XMPP IQs, SDP offer/answer cycles)
// about the call.
// enableSaveLogs: false,
// Enabling this will run the lib-jitsi-meet noise detection module which will
// notify the user if there is noise, other than voice, coming from the current
// selected microphone. The purpose it to let the user know that the input could
@@ -122,7 +120,7 @@ var config = {
// Valid values are in the range 6000 to 510000
// opusMaxAverageBitrate: 20000,
// Enables support for opus-red (redundancy for Opus).
// Enables redundancy for Opus
// enableOpusRed: false
// Video
@@ -304,11 +302,18 @@ var config = {
// Disables or enables RTX (RFC 4588) (defaults to false).
// disableRtx: false,
// Disables or enables TCC support in this client (default: enabled).
// Disables or enables TCC (the default is in Jicofo and set to true)
// (draft-holmer-rmcat-transport-wide-cc-extensions-01). This setting
// affects congestion control, it practically enables send-side bandwidth
// estimations.
// enableTcc: true,
// Disables or enables REMB support in this client (default: enabled).
// enableRemb: true,
// Disables or enables REMB (the default is in Jicofo and set to false)
// (draft-alvestrand-rmcat-remb-03). This setting affects congestion
// control, it practically enables recv-side bandwidth estimations. When
// both TCC and REMB are enabled, TCC takes precedence. When both are
// disabled, then bandwidth estimations are disabled.
// enableRemb: false,
// Enables ICE restart logic in LJM and displays the page reload overlay on
// ICE failure. Current disabled by default because it's causing issues with
@@ -318,11 +323,23 @@ var config = {
// TCC sequence numbers starting from 0.
// enableIceRestart: false,
// Defines the minimum number of participants to start a call (the default
// is set in Jicofo and set to 2).
// minParticipants: 2,
// Use TURN/UDP servers for the jitsi-videobridge connection (by default
// we filter out TURN/UDP because it is usually not needed since the
// bridge itself is reachable via UDP)
// useTurnUdp: false
// Enables / disables a data communication channel with the Videobridge.
// Values can be 'datachannel', 'websocket', true (treat it as
// 'datachannel'), undefined (treat it as 'datachannel') and false (don't
// open any channel).
// openBridgeChannel: true,
openBridgeChannel: 'websocket',
// UI
//
@@ -376,9 +393,6 @@ var config = {
// Document should be focused for this option to work
// enableAutomaticUrlCopy: false,
// Base URL for a Gravatar-compatible service. Defaults to libravatar.
// gravatarBaseURL: 'https://seccdn.libravatar.org/avatar/';
// Stats
//
@@ -591,9 +605,6 @@ var config = {
// If set to true all muting operations of remote participants will be disabled.
// disableRemoteMute: true,
// Enables support for lip-sync for this client (if the browser supports it).
// enableLipSync: false
/**
External API url used to receive branding specific information.
If there is no url set or there are missing fields, the defaults are applied.
@@ -616,12 +627,6 @@ var config = {
// otherwise the app doesn't render it.
// moderatedRoomServiceUrl: 'https://moderated.jitsi-meet.example.com',
// Hides the conference timer.
// hideConferenceTimer: true,
// Sets the conference subject
// subject: 'Conference Subject',
// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold
@@ -668,11 +673,12 @@ var config = {
disableAP
disableHPF
disableNS
enableLipSync
enableTalkWhileMuted
forceJVB121Ratio
forceTurnRelay
hiddenDomain
ignoreStartMuted
startBitrate
*/

View File

@@ -15,8 +15,9 @@
box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100vh;
height: calc(100vh - 200px);
width: 100vw;
margin: 100px 0px;
}
.filmstrip__videos .videocontainer {
@@ -94,7 +95,7 @@
border: 0;
box-sizing: border-box;
display: block;
margin: 2px;
margin: 5px;
}
video {

View File

@@ -1,7 +1,6 @@
platform :ios, '11.0'
workspace 'jitsi-meet'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
install! 'cocoapods', :deterministic_uuids => false
target 'jitsi-meet' do
project 'app/app.xcodeproj'

View File

@@ -293,10 +293,10 @@ PODS:
- React
- react-native-splash-screen (3.2.0):
- React
- react-native-webrtc (1.87.1):
- React-Core
- react-native-webview (11.0.2):
- react-native-webrtc (1.84.1):
- React-Core
- react-native-webview (10.9.0):
- React
- React-RCTActionSheet (0.61.5-jitsi.2):
- React-Core/RCTActionSheetHeaders (= 0.61.5-jitsi.2)
- React-RCTAnimation (0.61.5-jitsi.2):
@@ -562,8 +562,8 @@ SPEC CHECKSUMS:
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
react-native-webrtc: 40eca4cac200fda34fb843da07e3402211bbbd10
react-native-webview: b2542d6fd424bcc3e3b2ec5f854f0abb4ec86c87
react-native-webrtc: edd689b0d5a462d7a6f6f52bca3f9414fc0ee11c
react-native-webview: 6ee7868ca8eba635dbf7963986d1ab7959da0391
React-RCTActionSheet: bcbc311dc3b47bc8efb2737ff0940239a45789a9
React-RCTAnimation: 65f61080ce632f6dea23d52e354ffac9948396c6
React-RCTBlob: 70d88f7b68b5c44953cdb286ac2e36a7a509a97e
@@ -582,6 +582,6 @@ SPEC CHECKSUMS:
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
Yoga: 96b469c5e81ff51b917b92e8c3390642d4ded30c
PODFILE CHECKSUM: f6626cd705333112182cedbe175ae2f9006e8874
PODFILE CHECKSUM: f2400f8e5a52c4d91697cbacba6956569efc5ab8
COCOAPODS: 1.10.0
COCOAPODS: 1.9.3

View File

@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
@@ -13,6 +13,8 @@
0B412F211EDEE95300B1A0A6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0B412F201EDEE95300B1A0A6 /* Main.storyboard */; };
0B5418471F7C5D8C00A2DD86 /* MeetingRowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B5418461F7C5D8C00A2DD86 /* MeetingRowController.swift */; };
0B7001701F7C51CC005944F4 /* InCallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B70016F1F7C51CC005944F4 /* InCallController.swift */; };
0BD6B4371EF82A6B00D1F4CD /* WebRTC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */; };
0BD6B4381EF82A6B00D1F4CD /* WebRTC.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
0BEA5C291F7B8F73000D0AB4 /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0BEA5C271F7B8F73000D0AB4 /* Interface.storyboard */; };
0BEA5C2B1F7B8F73000D0AB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0BEA5C2A1F7B8F73000D0AB4 /* Assets.xcassets */; };
0BEA5C321F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0BEA5C311F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@@ -26,8 +28,6 @@
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
695AF3ED6F686F9C5EE40F9A /* libPods-jitsi-meet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 489E8EFE2C720D10F5961AEF /* libPods-jitsi-meet.a */; };
DE050389256E904600DEE3A5 /* WebRTC.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE050388256E904600DEE3A5 /* WebRTC.xcframework */; };
DE05038A256E904600DEE3A5 /* WebRTC.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DE050388256E904600DEE3A5 /* WebRTC.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
DE4C456121DE1E4E00EA0709 /* FIRUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = DE4C455F21DE1E4E00EA0709 /* FIRUtilities.m */; };
E588011722789D43008B0561 /* JitsiMeetContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E58801132278944E008B0561 /* JitsiMeetContext.swift */; };
E5C97B63227A1EB400199214 /* JitsiMeetCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5C97B62227A1EB400199214 /* JitsiMeetCommands.swift */; };
@@ -57,8 +57,8 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
DE05038A256E904600DEE3A5 /* WebRTC.xcframework in Embed Frameworks */,
0B26BE6F1EC5BC3C00EEFB41 /* JitsiMeet.framework in Embed Frameworks */,
0BD6B4381EF82A6B00D1F4CD /* WebRTC.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -117,10 +117,8 @@
4670A512A688E2DC34528282 /* Pods-jitsi-meet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-jitsi-meet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-jitsi-meet/Pods-jitsi-meet.debug.xcconfig"; sourceTree = "<group>"; };
489E8EFE2C720D10F5961AEF /* libPods-jitsi-meet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-jitsi-meet.a"; sourceTree = BUILT_PRODUCTS_DIR; };
B3B083EB1D4955FF0069CEE7 /* app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = app.entitlements; sourceTree = "<group>"; };
DE050388256E904600DEE3A5 /* WebRTC.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WebRTC.xcframework; path = "../../node_modules/react-native-webrtc/apple/WebRTC.xcframework"; sourceTree = "<group>"; };
DE4C455F21DE1E4E00EA0709 /* FIRUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRUtilities.m; sourceTree = "<group>"; };
DE4C456021DE1E4E00EA0709 /* FIRUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FIRUtilities.h; sourceTree = "<group>"; };
DEFDBBDB25656E3B00344B23 /* WebRTC.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WebRTC.xcframework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.xcframework"; sourceTree = "<group>"; };
E58801132278944E008B0561 /* JitsiMeetContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetContext.swift; sourceTree = "<group>"; };
E5C97B62227A1EB400199214 /* JitsiMeetCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetCommands.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -138,8 +136,8 @@
buildActionMask = 2147483647;
files = (
0B26BE6E1EC5BC3C00EEFB41 /* JitsiMeet.framework in Frameworks */,
0BD6B4371EF82A6B00D1F4CD /* WebRTC.framework in Frameworks */,
695AF3ED6F686F9C5EE40F9A /* libPods-jitsi-meet.a in Frameworks */,
DE050389256E904600DEE3A5 /* WebRTC.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -156,9 +154,7 @@
0B26BE711EC5BC4D00EEFB41 /* Frameworks */ = {
isa = PBXGroup;
children = (
DE050388256E904600DEE3A5 /* WebRTC.xcframework */,
0B26BE6D1EC5BC3C00EEFB41 /* JitsiMeet.framework */,
DEFDBBDB25656E3B00344B23 /* WebRTC.xcframework */,
0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */,
489E8EFE2C720D10F5961AEF /* libPods-jitsi-meet.a */,
);
@@ -294,6 +290,8 @@
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
0B26BE701EC5BC3C00EEFB41 /* Embed Frameworks */,
B35383AD1DDA0083008F406A /* Adjust embedded framework architectures */,
DE3A859324C701EA009B7D76 /* Copy WebRTC dSYM */,
0BB7DA181EC9E695007AAE98 /* Adjust ATS */,
DEF4813D224925A2002AD03A /* Copy Google Plist file */,
DE11877A21EE09640078D059 /* Setup Google reverse URL handler */,
@@ -422,6 +420,20 @@
shellPath = /bin/sh;
shellScript = "../scripts/run-packager.sh\n";
};
B35383AD1DDA0083008F406A /* Adjust embedded framework architectures */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Adjust embedded framework architectures";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "../scripts/fixup-frameworks.sh\n";
};
B6607F42A5CF0C76E98929E2 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -462,6 +474,24 @@
shellPath = /bin/sh;
shellScript = "INFO_PLIST=\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\nGOOGLE_PLIST=\"$PROJECT_DIR/GoogleService-Info.plist\"\n\nif [[ -f $GOOGLE_PLIST ]]; then\n REVERSED_CLIENT_ID=$(/usr/libexec/PlistBuddy -c \"Print :REVERSED_CLIENT_ID:\" $GOOGLE_PLIST)\n /usr/libexec/PlistBuddy -c \"Set :CFBundleURLTypes:1:CFBundleURLSchemes:0 $REVERSED_CLIENT_ID\" $INFO_PLIST\nfi\n";
};
DE3A859324C701EA009B7D76 /* Copy WebRTC dSYM */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Copy WebRTC dSYM";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -x\n\nif [[ \"${CONFIGURATION}\" != \"Debug\" ]]; then\n cp -r ../../node_modules/react-native-webrtc/ios/WebRTC.dSYM ${DWARF_DSYM_FOLDER_PATH}/\nfi\n";
};
DE4F6D6E22005C0400DE699E /* Setup Dropbox */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -622,8 +652,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = watchos;
SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 4.0;
@@ -650,11 +679,7 @@
DEVELOPMENT_TEAM = FC967L3QRG;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = watchos/extension/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.watchkit.extension;
PRODUCT_NAME = "${TARGET_NAME}";
SDKROOT = watchos;
@@ -688,17 +713,12 @@
DEVELOPMENT_TEAM = FC967L3QRG;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = watchos/extension/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.watchkit.extension;
PRODUCT_NAME = "${TARGET_NAME}";
SDKROOT = watchos;
SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 4.0;
@@ -709,6 +729,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 4670A512A688E2DC34528282 /* Pods-jitsi-meet.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDebug;
CODE_SIGN_ENTITLEMENTS = app.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -717,11 +738,16 @@
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = FC967L3QRG;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = src/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"../../node_modules/react-native-webrtc/ios",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
INFOPLIST_FILE = src/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
@@ -738,6 +764,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 09AA3B93E4CC62D84B424690 /* Pods-jitsi-meet.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconRelease;
CODE_SIGN_ENTITLEMENTS = app.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
@@ -745,11 +772,16 @@
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = FC967L3QRG;
ENABLE_BITCODE = YES;
INFOPLIST_FILE = src/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"../../node_modules/react-native-webrtc/ios",
);
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
INFOPLIST_FILE = src/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",

View File

@@ -45,8 +45,6 @@
#endif
}];
[jitsiMeet application:application didFinishLaunchingWithOptions:launchOptions];
// Initialize Crashlytics and Firebase if a valid GoogleService-Info.plist file was provided.
if ([FIRUtilities appContainsRealServiceInfoPlist]) {
NSLog(@"Enabling Firebase");
@@ -57,6 +55,8 @@
ViewController *rootController = (ViewController *)self.window.rootViewController;
[jitsiMeet showSplashScreen:rootController.view];
[jitsiMeet application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>20.6.0</string>
<string>20.5.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>20.6.0</string>
<string>20.5.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UISupportedInterfaceOrientations</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>20.6.0</string>
<string>20.5.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CLKComplicationPrincipalClass</key>

15
ios/scripts/bitcode.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/bash
# This script will download a bitcode build of the WebRTC framework, if needed.
if [[ ! "$CONFIGURATION" = "Debug" ]]; then
RN_WEBRTC="$SRCROOT/../../node_modules/react-native-webrtc"
if otool -arch arm64 -l $RN_WEBRTC/ios/WebRTC.framework/WebRTC | grep -q LLVM; then
echo "WebRTC framework has bitcode"
else
echo "WebRTC framework has NO bitcode"
$RN_WEBRTC/tools/downloadBitcode.sh
fi
fi

39
ios/scripts/fixup-frameworks.sh Executable file
View File

@@ -0,0 +1,39 @@
#!/bin/bash
# This script gets executed from Xcode to fixup the embedded frameworks and
# bundle the necessary architectures.
APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
# This script loops through the frameworks embedded in the application and
# removes unused architectures.
find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK
do
FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)
FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"
EXTRACTED_ARCHS=()
for ARCH in $ARCHS
do
if lipo -info "$FRAMEWORK_EXECUTABLE_PATH" | grep -q -v "^Non-fat"
then
echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME"
lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
fi
done
if [ -n "$EXTRACTED_ARCHS" ]
then
echo "Merging extracted architectures: ${ARCHS}"
lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
rm "${EXTRACTED_ARCHS[@]}"
echo "Replacing original executable with thinned version"
rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"
fi
done

View File

@@ -24,36 +24,8 @@ popd
# Build the SDK
pushd ${PROJECT_REPO}
rm -rf ios/sdk/out
xcodebuild clean \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeet
xcodebuild archive \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeet \
-configuration Release \
-sdk iphonesimulator \
-destination='generic/platform=iOS Simulator' \
-archivePath ios/sdk/out/ios-simulator \
VALID_ARCHS=x86_64 \
ENABLE_BITCODE=NO \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
xcodebuild archive \
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeet \
-configuration Release \
-sdk iphoneos \
-destination='generic/platform=iOS' \
-archivePath ios/sdk/out/ios-device \
VALID_ARCHS=arm64 \
ENABLE_BITCODE=NO \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
xcodebuild -create-xcframework \
-framework ios/sdk/out/ios-device.xcarchive/Products/Library/Frameworks/JitsiMeet.framework \
-framework ios/sdk/out/ios-simulator.xcarchive/Products/Library/Frameworks/JitsiMeet.framework \
-output ios/sdk/out/JitsiMeet.xcframework
rm -rf ios/sdk/JitsiMeet.framework
xcodebuild -workspace ios/jitsi-meet.xcworkspace -scheme JitsiMeet -destination='generic/platform=iOS' -configuration Release ENABLE_BITCODE=NO clean archive
if [[ $DO_GIT_TAG == 1 ]]; then
git tag ios-sdk-${SDK_VERSION}
fi
@@ -62,8 +34,12 @@ popd
pushd ${RELEASE_REPO}
# Put the new files in the repo
cp -a ${PROJECT_REPO}/ios/sdk/out/JitsiMeet.xcframework Frameworks/
cp -a ${PROJECT_REPO}/node_modules/react-native-webrtc/apple/WebRTC.xcframework Frameworks/
cp -r ${PROJECT_REPO}/ios/sdk/JitsiMeet.framework Frameworks/
cp -r ${PROJECT_REPO}/node_modules/react-native-webrtc/ios/WebRTC.framework Frameworks/
# Strip bitcode
xcrun bitcode_strip -r Frameworks/JitsiMeet.framework/JitsiMeet -o Frameworks/JitsiMeet.framework/JitsiMeet
xcrun bitcode_strip -r Frameworks/WebRTC.framework/WebRTC -o Frameworks/WebRTC.framework/WebRTC
# Add all files to git
if [[ $DO_GIT_TAG == 1 ]]; then

View File

@@ -81,6 +81,8 @@
0BCA495C1EC4B6C600B793EE /* AudioMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioMode.m; sourceTree = "<group>"; };
0BCA495D1EC4B6C600B793EE /* POSIX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = POSIX.m; sourceTree = "<group>"; };
0BCA495E1EC4B6C600B793EE /* Proximity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Proximity.m; sourceTree = "<group>"; };
0BCA49631EC4B76D00B793EE /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.framework"; sourceTree = "<group>"; };
0BCA496B1EC4BBF900B793EE /* jitsi.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = jitsi.ttf; path = ../../fonts/jitsi.ttf; sourceTree = "<group>"; };
0BD906E51EC0C00300C8C18E /* JitsiMeet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JitsiMeet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeet.h; sourceTree = "<group>"; };
0BD906E91EC0C00300C8C18E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -151,6 +153,7 @@
0BC4B8681F8C01E100CE8B21 /* CallKitIcon.png */,
C6245F5B2053091D0040BE68 /* image-resize@2x.png */,
C6245F5C2053091D0040BE68 /* image-resize@3x.png */,
0BCA496B1EC4BBF900B793EE /* jitsi.ttf */,
75635B0820751D6D00F29C9F /* joined.wav */,
75635B0920751D6D00F29C9F /* left.wav */,
C30F88D1CB0F4F5593216D24 /* liveStreamingOff.mp3 */,
@@ -237,6 +240,7 @@
0B93EF7A1EC608550030D24D /* CoreText.framework */,
0BB9AD781F5EC6D7001C08DB /* Intents.framework */,
03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */,
0BCA49631EC4B76D00B793EE /* WebRTC.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -317,6 +321,7 @@
buildConfigurationList = 0BD906ED1EC0C00300C8C18E /* Build configuration list for PBXNativeTarget "JitsiMeet" */;
buildPhases = (
26796D8589142D80C8AFDA51 /* [CP] Check Pods Manifest.lock */,
DE3D81D6228B50FB00A6C149 /* Bitcode */,
0BD906E01EC0C00300C8C18E /* Sources */,
0BD906E11EC0C00300C8C18E /* Frameworks */,
0BD906E21EC0C00300C8C18E /* Headers */,
@@ -449,6 +454,24 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources.sh\"\n";
showEnvVarsInLog = 0;
};
DE3D81D6228B50FB00A6C149 /* Bitcode */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = Bitcode;
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "../scripts/bitcode.sh\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -612,6 +635,7 @@
baseConfigurationReference = 98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
@@ -622,6 +646,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = src/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeetSDK.ios;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -630,6 +655,7 @@
SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 5.0;
};
name = Debug;
@@ -639,6 +665,7 @@
baseConfigurationReference = 9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
@@ -649,6 +676,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = YES;
INFOPLIST_FILE = src/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeetSDK.ios;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -656,6 +684,7 @@
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 5.0;
};
name = Release;

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
@@ -72,5 +72,23 @@
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
<PostActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "exec &gt; /tmp/${PROJECT_NAME}_archive.log 2&gt;&amp;1&#10;&#10;UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal&#10;&#10;if [ &quot;true&quot; == ${ALREADYINVOKED:-false} ]&#10;then&#10;echo &quot;RECURSION: Detected, stopping&quot;&#10;else&#10;export ALREADYINVOKED=&quot;true&quot;&#10;&#10;# make sure the output directory exists&#10;mkdir -p &quot;${UNIVERSAL_OUTPUTFOLDER}&quot;&#10;&#10;echo &quot;Building for iPhoneSimulator&quot;&#10;xcodebuild -workspace &quot;${WORKSPACE_PATH}&quot; -scheme &quot;${TARGET_NAME}&quot; -configuration ${CONFIGURATION} -sdk iphonesimulator -destination &apos;platform=iOS Simulator,name=iPhone 8&apos; ONLY_ACTIVE_ARCH=NO ARCHS=&apos;x86_64&apos; BUILD_DIR=&quot;${BUILD_DIR}&quot; BUILD_ROOT=&quot;${BUILD_ROOT}&quot; ENABLE_BITCODE=YES OTHER_CFLAGS=&quot;-fembed-bitcode&quot; BITCODE_GENERATION_MODE=bitcode build&#10;&#10;# Step 1. Copy the framework structure (from iphoneos build) to the universal folder&#10;echo &quot;Copying to output folder&quot;&#10;cp -R &quot;${BUILD_DIR}/${CONFIGURATION}-iphoneos/${FULL_PRODUCT_NAME}&quot; &quot;${UNIVERSAL_OUTPUTFOLDER}/&quot;&#10;&#10;# Step 2. Copy Swift modules from iphonesimulator build (if it exists) to the copied framework directory&#10;SIMULATOR_SWIFT_MODULES_DIR=&quot;${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/Modules/${TARGET_NAME}.swiftmodule/.&quot;&#10;if [ -d &quot;${SIMULATOR_SWIFT_MODULES_DIR}&quot; ]; then&#10;cp -R &quot;${SIMULATOR_SWIFT_MODULES_DIR}&quot; &quot;${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/Modules/${TARGET_NAME}.swiftmodule&quot;&#10;fi&#10;&#10;# Step 3. Create universal binary file using lipo and place the combined executable in the copied framework directory&#10;echo &quot;Combining executables&quot;&#10;lipo -create -output &quot;${UNIVERSAL_OUTPUTFOLDER}/${EXECUTABLE_PATH}&quot; &quot;${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${EXECUTABLE_PATH}&quot; &quot;${BUILD_DIR}/${CONFIGURATION}-iphoneos/${EXECUTABLE_PATH}&quot;&#10;&#10;fi&#10;&#10;# Step 4. Convenience step to copy the framework to the project&amp;apos;s directory&#10;echo &quot;Copying to project dir&amp;quot&quot;&#10;yes | cp -Rf ${UNIVERSAL_OUTPUTFOLDER}/${FULL_PRODUCT_NAME} ${PROJECT_DIR}&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0BD906E41EC0C00300C8C18E"
BuildableName = "JitsiMeet.framework"
BlueprintName = "JitsiMeet"
ReferencedContainer = "container:sdk.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PostActions>
</ArchiveAction>
</Scheme>

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.12.0</string>
<string>2.11.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -18,29 +18,6 @@
import CallKit
import Foundation
public protocol CXProviderProtocol: class {
var configuration: CXProviderConfiguration { get set }
func setDelegate(_ delegate: CXProviderDelegate?, queue: DispatchQueue?)
func reportNewIncomingCall(with UUID: UUID, update: CXCallUpdate, completion: @escaping (Error?) -> Void)
func reportCall(with UUID: UUID, updated update: CXCallUpdate)
func reportCall(with UUID: UUID, endedAt dateEnded: Date?, reason endedReason: CXCallEndedReason)
func reportOutgoingCall(with UUID: UUID, startedConnectingAt dateStartedConnecting: Date?)
func reportOutgoingCall(with UUID: UUID, connectedAt dateConnected: Date?)
func invalidate()
}
public protocol CXCallControllerProtocol: class {
var calls: [CXCall] { get }
func request(_ transaction: CXTransaction, completion: @escaping (Error?) -> Swift.Void)
}
extension CXProvider: CXProviderProtocol {}
extension CXCallController: CXCallControllerProtocol {
public var calls: [CXCall] {
return callObserver.calls
}
}
/// JitsiMeet CallKit proxy
// NOTE: The methods this class exposes are meant to be called in the UI thread.
// All delegate methods called by JMCallKitEmitter will be called in the UI thread.
@@ -49,17 +26,11 @@ extension CXCallController: CXCallControllerProtocol {
private override init() {}
// MARK: - CallKit proxy
public static var callKitProvider: CXProviderProtocol?
public static var callKitCallController: CXCallControllerProtocol?
private static var provider: CXProviderProtocol {
callKitProvider ?? defaultProvider
}
private static var callController: CXCallControllerProtocol {
callKitCallController ?? defaultCallController
}
private static var provider: CXProvider = {
let configuration = CXProviderConfiguration(localizedName: "")
return CXProvider(configuration: configuration)
}()
private static var providerConfiguration: CXProviderConfiguration? {
didSet {
@@ -68,15 +39,10 @@ extension CXCallController: CXCallControllerProtocol {
provider.setDelegate(emitter, queue: nil)
}
}
private static let defaultCallController: CXCallController = {
private static let callController: CXCallController = {
return CXCallController()
}()
private static var defaultProvider: CXProvider = {
let configuration = CXProviderConfiguration(localizedName: "")
return CXProvider(configuration: configuration)
}()
private static let emitter: JMCallKitEmitter = {
return JMCallKitEmitter()
@@ -86,16 +52,10 @@ extension CXCallController: CXCallControllerProtocol {
/// Defaults to enabled, set to false when you don't want to use CallKit.
@objc public static var enabled: Bool = true {
didSet {
if callKitProvider == nil {
provider.invalidate()
}
provider.invalidate()
if enabled {
guard isProviderConfigured() else { return }
if callKitProvider == nil {
defaultProvider = CXProvider(configuration: providerConfiguration!)
}
guard isProviderConfigured() else { return; }
provider = CXProvider(configuration: providerConfiguration!)
provider.setDelegate(emitter, queue: nil)
} else {
provider.setDelegate(nil, queue: nil)
@@ -132,18 +92,19 @@ extension CXCallController: CXCallControllerProtocol {
}
@objc public static func hasActiveCallForUUID(_ callUUID: String) -> Bool {
let activeCallForUUID = callController.calls.first {
let activeCallForUUID = callController.callObserver.calls.first {
$0.uuid == UUID(uuidString: callUUID)
}
guard activeCallForUUID != nil else { return false }
return true
}
@objc public static func reportNewIncomingCall(UUID: UUID,
handle: String?,
displayName: String?,
hasVideo: Bool,
completion: @escaping (Error?) -> Void) {
@objc public static func reportNewIncomingCall(
UUID: UUID,
handle: String?,
displayName: String?,
hasVideo: Bool,
completion: @escaping (Error?) -> Void) {
guard enabled else { return }
let callUpdate = makeCXUpdate(handle: handle,
@@ -171,6 +132,7 @@ extension CXCallController: CXCallControllerProtocol {
endedAt dateEnded: Date?,
reason endedReason: CXCallEndedReason) {
guard enabled else { return }
provider.reportCall(with: UUID,
endedAt: dateEnded,
reason: endedReason)
@@ -180,6 +142,7 @@ extension CXCallController: CXCallControllerProtocol {
with UUID: UUID,
startedConnectingAt dateStartedConnecting: Date?) {
guard enabled else { return }
provider.reportOutgoingCall(with: UUID,
startedConnectingAt: dateStartedConnecting)
}

View File

@@ -1,50 +1,27 @@
{
"en": "Angličtina",
"af": "Afrikánština",
"ar": "Arabština",
"az": "Ázerbájdžánština",
"af": "",
"az": "",
"bg": "Bulharština",
"ca": "Katalánština",
"cs": "Čeština",
"da": "Dánština",
"cs": "",
"de": "Němčina",
"el": "Řečtina",
"enGB": "Angličtina (Spojené království)",
"el": "",
"eo": "Esperanto",
"es": "Španělština",
"esUS": "Španělština (Latinská Amerika)",
"et": "Estonština",
"eu": "Baskičtina",
"fi": "Finština",
"fr": "Francouština",
"frCA": "Francouzština (Kanada)",
"he": "Hebrejština",
"mr":"Maráthština",
"hr": "Chorvatština",
"hu": "Maďarština",
"hy": "Arménština",
"id": "Indonéština",
"it": "Italština",
"ja": "Japonština",
"kab": "Kabylština",
"ko": "Korejština",
"lt": "Litevština",
"nl": "Nizozemština",
"ja": "",
"ko": "",
"nb": "Norština Bokmal",
"oc": "Okcitánština",
"pl": "Polština",
"ptBR": "Portugalština (Brazílie)",
"ptBR": "Portugalština (Brazilská)",
"ru": "Ruština",
"ro": "Rumunština",
"sc": "Sardinština",
"sk": "Slovenština",
"sl": "Slovinština",
"sr": "Srbština",
"sv": "Švédština",
"th": "Thajština",
"tr": "Turečtina",
"uk": "Ukrajinština",
"vi": "Vietnamština",
"zhCN": "Čínština (Čína)",
"zhTW": "Čínština (Taiwan)"
}
"vi": "",
"zhCN": "Čínština (Čína)"
}

View File

@@ -28,7 +28,6 @@
"kab": "Kabyle",
"ko": "Korean",
"lt": "Lithuanian",
"lv": "Latvian",
"nl": "Dutch",
"oc": "Occitan",
"pl": "Polish",

File diff suppressed because it is too large Load Diff

View File

@@ -707,7 +707,7 @@
"kick": "Teilnehmer entfernen",
"lobbyButton": "Lobbymodus ein-/ausschalten",
"localRecording": "Lokale Aufzeichnungssteuerelemente ein-/ausschalten",
"lockRoom": "Konferenzpasswort ein-/ausschalten",
"lockRoom": "Konferenzpasswort ein-/auschalten",
"moreActions": "Menü „Weitere Aktionen“ ein-/ausschalten",
"moreActionsMenu": "Menü „Weitere Aktionen“",
"moreOptions": "Menü „Weitere Optionen“",

View File

@@ -1,43 +1,28 @@
{
"addPeople": {
"add": "Invita",
"addContacts": "Invita tuoi contatti",
"copyInvite": "Copia invito della riunione",
"copyLink": "Copia collegamento della riunione",
"copyStream": "Copia collegamento della diretta",
"countryNotSupported": "Non supportiamo ancora questa destinazione.",
"countryReminder": "Stai chiamando fuori dagli Stati Uniti? Assicurati d'inserire il prefisso internazionale!",
"defaultEmail": "Tua Email di default",
"disabled": "Non puoi invitare persone.",
"failedToAdd": "L'aggiunta di nuove persone è fallita",
"footerText": "La chiamata all'esterno è disabilitata.",
"googleEmail": "Email Google",
"inviteMoreHeader": "Sei l'unico presente alla riunione",
"inviteMoreMailSubject": "Unisciti alla riunione {{appName}}",
"inviteMorePrompt": "Invita altra gente",
"linkCopied": "Collegamento copiato negli appunti",
"loading": "Sto cercando persone e numeri di telefono",
"loadingNumber": "Sto validando il numero di telefono",
"loadingPeople": "Sto cercando le persone da invitare",
"noResults": "Nessun risultato corrispondente",
"noValidNumbers": "Per favore inserire un numero di telefono",
"outlookEmail": "Email Outlook",
"searchNumbers": "Aggiungi numeri di telefono",
"searchPeople": "Cerca persone",
"searchPeopleAndNumbers": "Cerca persone o aggiungi i loro numeri di telefono",
"shareInvite": "Condividi invito alla riunione",
"shareLink": "Condividi il collegamento alla riunione per invitare altri",
"shareStream": "Condividi il collegamento alla diretta",
"telephone": "Telefono: {{number}}",
"title": "Invita persone a questa riunione",
"yahooEmail": "Email Yahoo"
"title": "Invita persone a questo meeting"
},
"audioDevices": {
"bluetooth": "Bluetooth",
"headphones": "Cuffie",
"phone": "Telefono",
"speaker": "Vivavoce",
"none": "Nessun disposistivo audio esistente"
"none": ""
},
"audioOnly": {
"audioOnly": "Solo audio"
@@ -62,25 +47,20 @@
},
"chat": {
"error": "Errore: il tuo messaggio “{{originalText}}” non e stato inviato. Motivo: {{error}}",
"fieldPlaceHolder": "Scrivi qui il tuo messaggio",
"messagebox": "Digitare un messaggio",
"messageTo": "Messaggio privato per {{recipient}}",
"noMessagesMessage": "Non ci sono ancora messaggi nella riunione. Comincia una conversazione, qui!",
"nickname": {
"popover": "Scegli un nickname",
"title": "Inserire un nickname per utilizzare la chat"
},
"privateNotice": "Messaggio privato per {{recipient}}",
"title": "Chat",
"you": "tu"
},
"chromeExtensionBanner": {
"installExtensionText": "Installa un'estensione per integrare Google Calendar e Office 365",
"buttonText": "Installa l'estensione Chrome",
"dontShowAgain": "Non mostrare più questo messaggio"
"you": "",
"privateNotice": "",
"noMessagesMessage": "",
"messageTo": "",
"fieldPlaceHolder": ""
},
"connectingOverlay": {
"joiningRoom": "Collegamento alla riunione in corso…"
"joiningRoom": "Collegamento al tuo meeting in corso…"
},
"connection": {
"ATTACHED": "Collegato",
@@ -92,27 +72,24 @@
"DISCONNECTED": "Disconnesso",
"DISCONNECTING": "Disconnessione in corso",
"ERROR": "Errore",
"FETCH_SESSION_ID": "Sto ottenendo ID di sessione...",
"GET_SESSION_ID_ERROR": "Id dell'errore di sessione: {{code}}",
"GOT_SESSION_ID": "Ricevuto ID di sessione",
"LOW_BANDWIDTH": "Il video per {{displayName}} è stato interrotto per risparmiare banda"
"RECONNECTING": "Si è verificato un problema di rete. Riconnessione...",
"LOW_BANDWIDTH": "",
"GOT_SESSION_ID": "",
"GET_SESSION_ID_ERROR": "",
"FETCH_SESSION_ID": ""
},
"connectionindicator": {
"address": "Indirizzo:",
"audio_ssrc": "Audio SSRC:",
"bandwidth": "Banda stimata:",
"bitrate": "Bitrate:",
"bridgeCount": "Contatore server:",
"codecs": "Codec (A/V): ",
"connectedTo": "Connesso a:",
"e2e_rtt": "E2E RTT:",
"framerate": "Fotogrammi al secondo:",
"less": "Mostra meno",
"localaddress": "Indirizzo locale:",
"localaddress_plural": "Indirizzi locali:",
"localport": "Porta locale:",
"localport_plural": "Porte locali:",
"maxEnabledResolution": "manda max",
"more": "Mostra di più",
"packetloss": "Perdita pacchetti:",
"quality": {
@@ -127,12 +104,11 @@
"remoteport": "Porta remota:",
"remoteport_plural": "Porte remote:",
"resolution": "Risoluzione:",
"savelogs": "Salva log",
"participant_id": "Id participante:",
"status": "Connessione:",
"transport": "Trasporto:",
"transport_plural": "Trasporti:",
"video_ssrc": "Video SSRC:"
"turn": "(ruota)",
"e2e_rtt": ""
},
"dateUtils": {
"earlier": "Prima",
@@ -142,7 +118,7 @@
"deepLinking": {
"appNotInstalled": "Per partecipare a questo meeting sul tuo telefono ti serve l'app mobile di {{app}}",
"description": "Non è successo nulla? Abbiamo provato ad avviare la tua videoconferenza sull'app desktop di {{app}}. Prova di nuovo o avviala nell'app web di {{app}}.",
"descriptionWithoutWeb": "Non è successo niente? Abbiamo provato ad avviare la riunione nell'app per desktop {{app}}",
"descriptionWithoutWeb": "",
"downloadApp": "Scarica l'app",
"ifDoNotHaveApp": "Se non hai ancora l'app:",
"ifHaveApp": "Se hai già l'app:",
@@ -152,7 +128,6 @@
"tryAgainButton": "Prova di nuovo sul desktop"
},
"defaultLink": "es. {{url}}",
"defaultNickname": "es. Anna Rossi",
"deviceError": {
"cameraError": "Impossibile accedere alla videocamera",
"cameraPermission": "Errore nell'ottenere i permessi per la videocamera",
@@ -167,9 +142,8 @@
},
"dialog": {
"accessibilityLabel": {
"liveStreaming": "Diretta streaming"
"liveStreaming": "Diretta"
},
"add": "Aggiungi",
"allow": "Consenti",
"alreadySharedVideoMsg": "Un altro utente sta condividendo un video. Questa conferenza permette di condividere un solo video alla volta.",
"alreadySharedVideoTitle": "È permesso un solo video alla volta",
@@ -195,26 +169,25 @@
"connectErrorWithMsg": "Oops! Qualcosa è andato storto e non ti puoi collegare alla conferenza: {{msg}}",
"connecting": "Connessione",
"contactSupport": "Contatta il supporto",
"copied": "Copiato",
"copy": "Copia",
"dismiss": "Scarta",
"displayNameRequired": "Tutti devono avere un nome",
"done": "Fatto",
"e2eeDescription": "La crittografia punto-a-punto al momento è SPERIMENTALE. Tieni presente che attivandola disabiliterai i servizi svolti dal server, come: la registrazione su Dropobox, le dirette streaming e la partecipazione usando solo telefoni. Tieni anche presente che la riunione funzionerà solo per chi si collega usando browser che supportano flussi inseribili (insertable streams).",
"e2eeLabel": "Attiva la crittografia punto-a-punto",
"e2eeWarning": "ATTENZIONE: non tutti i partecipanti a questa riunione sembrano supportare le funzionalità di crittografia punto-a-punto. Se la attivi, non potranno sentirti, o vederti.",
"enterDisplayName": "Inserisci qui il tuo nome",
"enterDisplayName": "Inserisci il nome da visualizzare",
"error": "Errore",
"externalInstallationMsg": "Devi installare la nostra estensione per la condivisione desktop.",
"externalInstallationTitle": "Richiesta estensione",
"goToStore": "Vai al negozio on-line",
"gracefulShutdown": "Il nostro servizio è al momento spento per manutenzione. Si prega di riprovare più tardi.",
"grantModeratorDialog": "Sei sicuro di voler rendere moderatore questo partecipante?",
"grantModeratorTitle": "Autorizza moderatore",
"IamHost": "Sono l'organizzatore",
"incorrectRoomLockPassword": "Password errata",
"incorrectRoomLockPassword": "",
"incorrectPassword": "Nome utente o password errati",
"inlineInstallationMsg": "Devi installare la nostra estensione per la condivisione desktop.",
"inlineInstallExtension": "Installa adesso",
"internalError": "Ops! Qualcosa è andato storto. Questo è l'errore: {{error}}",
"internalErrorTitle": "Errore interno",
"kickMessage": "Acc! Sei stato espulso dal meeting!",
"kickParticipantButton": "Butta fuori",
"kickParticipantButton": "Espelli",
"kickParticipantDialog": "Sei sicuro di voler espellere questo partecipante?",
"kickParticipantTitle": "Espellere questi partecipante?",
"kickTitle": "Espulso dal meeting",
@@ -230,28 +203,21 @@
"maxUsersLimitReachedTitle": "Raggiunto limite partecipanti",
"micConstraintFailedError": "Il tuo microfono non soddisfa alcuni dei requisiti richiesti.",
"micNotFoundError": "Microfono non trovato.",
"micNotSendingData": "Non riusciamo a ricevere suoni dal microfono scelto. Prova a selezionare nelle impostazioni un microfono diverso, o a riavviare l'applicazione.",
"micNotSendingData": "Non riusciamo a ricevere suoni dal microfono scelto. Prova a selezionare nelle impostazioni un microfono diverso, o a riavvare l'applicazione.",
"micNotSendingDataTitle": "Impossibile accedere al microfono",
"micPermissionDeniedError": "Non hai concesso il permesso di usare il microfono. Puoi comunque partecipare alla conferenza ma gli altri non potranno sentirti. Usa il bottone a forma di telecamera nella barra degli indirizzi per cambiare impostazioni.",
"micUnknownError": "Impossibile usare il microfono per un motivo sconosciuto.",
"muteEveryoneElseDialog": "Una volta zittiti, non potrai riattivargli i microfoni, ma loro potranno farlo in qualsiasi momento.",
"muteEveryoneElseTitle": "Zittisco tutti eccetto {{whom}}?",
"muteEveryoneDialog": "Sei sicuro di voler zittire tutti? Non potrai riattivar loro il microfono, ma loro potranno farlo in qualsiasi momento.",
"muteEveryoneTitle": "Zittisco tutti?",
"muteEveryoneSelf": "te stesso",
"muteEveryoneStartMuted": "Tutti cominciano a microfono spento, d'adessp in avanti",
"muteParticipantBody": "Non sarai in grado di riattivare il loro microfono, ma loro potranno riattivarlo in qualsiasi momento.",
"muteParticipantButton": "Zittisci",
"muteParticipantDialog": "Sei sicuro di voler zittire questo partecipante? Saranno lui a doversi riattivare l'audio, per parlare.",
"muteParticipantTitle": "Zittisco questo partecipante?",
"Ok": "OK",
"passwordLabel": "La riunione è stata bloccata da un partecipante. Immetti la $t(lockRoomPassword) per collegarti, per favore.",
"passwordNotSupported": "Le password per le riunioni non sono supportate.",
"passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) non supportato",
"passwordRequired": "E' richiesto $t(lockRoomPasswordUppercase)",
"popupError": "Il tuo browser sta bloccando i pop-up da questo sito. Per favore abilita i pop-up dalle impostazioni di sicurezza del browser e riprova.",
"muteParticipantBody": "Tu non sarai in grado di riattivare il loro audio, ma loro potranno riattivarlo in qualsiasi momento.",
"muteParticipantButton": "Silenzia partecipante",
"muteParticipantDialog": "Sei sicuro di voler disattivare l'audio di questo partecipante? Saranno loro a doversi riattivare l'audio, per parlare.",
"muteParticipantTitle": "Silenzio questo partecipante?",
"Ok": "Ok",
"passwordLabel": "",
"passwordNotSupported": "Le password per le videoconferenze non sono supportate.",
"passwordNotSupportedTitle": "",
"passwordRequired": "",
"popupError": "Il tuo browser sta bloccando i pop-up da questo sito. Per favore abilità i pop-up dalle impostazioni di sicurezza del browser e riprova.",
"popupErrorTitle": "Pop-up bloccato",
"readMore": "altro",
"recording": "Registrazione",
"recordingDisabledForGuestTooltip": "Gli ospiti non possono avviare una registrazione.",
"recordingDisabledTooltip": "Registrazione disabilitata.",
@@ -270,14 +236,11 @@
"reservationError": "Errore di sistema in prenotazione",
"reservationErrorMsg": "Codice di errore: {{code}}, messaggio: {{msg}}",
"retry": "Riprova",
"screenSharingAudio": "Condividi audio",
"screenSharingFailed": "Ops! Non è stato possibile avviare la condivisione dello schermo!",
"screenSharingFailedTitle": "Condivisione dello schermo fallita!",
"screenSharingPermissionDeniedError": "Qualcosa non funziona nei permessi di condivisione dello schermo. Ricarica e prova ancora, autorizzando la condivisione.",
"sendPrivateMessage": "Hai ricevuto un messaggio privato, poco fa. Vorresti rispondergli privatamente, o vuoi mandare la risposta al gruppo?",
"sendPrivateMessageCancel": "Invia al gruppo",
"sendPrivateMessageOk": "Invia privatamente",
"sendPrivateMessageTitle": "Mando privatamente?",
"screenSharingFailedToInstall": "Oh! Non è stato possibile installare l'estensione per la condivisione schermo.",
"screenSharingFailedToInstallTitle": "Impossibile installare l'estensione per la condivisione schermo",
"screenSharingFirefoxPermissionDeniedError": "Qualcosa è andato storto mentre cercavamo di condividere il tuo schermo. Assicurati di averci dato il premesso di condivisione.",
"screenSharingFirefoxPermissionDeniedTitle": "Ops! Non siamo stati in grado di avviare la condivisione schermo!",
"screenSharingPermissionDeniedError": "Oops! Qualcosa è andato storto con le impostazioni dell'estensione per la condivisione dello schermo. Ricarica la pagina e prova di nuovo.",
"serviceUnavailable": "Servizio non disponibile",
"sessTerminated": "Chiamata terminata",
"Share": "Condividi",
@@ -285,6 +248,7 @@
"shareVideoTitle": "Condividi un video",
"shareYourScreen": "Condividi schermo",
"shareYourScreenDisabled": "Condivisione schermo disabilitata.",
"shareYourScreenDisabledForGuest": "Gli ospiti non possono condividere lo schermo.",
"startLiveStreaming": "Inizia una diretta",
"startRecording": "Inizia a registrare",
"startRemoteControlErrorMessage": "Si è verificato un errore cercando di avviare la sessione di controllo remoto!",
@@ -305,19 +269,21 @@
"WaitForHostMsgWOk": "La conferenza <b>{{room}}</b> non è ancora cominciata. Se sei l'organizzatore, allora premi OK per autenticarti. Altrimenti, aspetta l'arrivo dell'organizzatore.",
"WaitingForHost": "In attesa dell'organizzatore ...",
"Yes": "Sì",
"yourEntireScreen": "Schermo intero"
"yourEntireScreen": "Schermo intero",
"sendPrivateMessageTitle": "",
"sendPrivateMessageOk": "",
"sendPrivateMessageCancel": "",
"sendPrivateMessage": "",
"screenSharingAudio": "",
"muteEveryoneStartMuted": "",
"muteEveryoneSelf": "",
"muteEveryoneTitle": "",
"muteEveryoneDialog": "",
"muteEveryoneElseTitle": "",
"muteEveryoneElseDialog": ""
},
"dialOut": {
"statusMessage": "è {{status}}"
},
"documentSharing": {
"title": "Documento condiviso"
},
"e2ee": {
"labelToolTip": "Le comunicazioni audio e video di questa chiamata, sono crittografate dall'origine alla destinazione"
},
"embedMeeting": {
"title": "Incorpora questa riunione altrove"
"statusMessage": ora {{status}}"
},
"feedback": {
"average": "Media",
@@ -343,14 +309,14 @@
"country": "Paese",
"dialANumber": "Per collegarti telefonicamente al meeting, chiama uno di questi numeri e metti il pin.",
"dialInConferenceID": "PIN:",
"dialInNotSupported": "Spiacenti, la partecipazione solo telefonica non è supportata attualmente",
"dialInNotSupported": "Spiacenti, la partecipazionne solo telefonica non è supportata attualmente",
"dialInNumber": "Componi:",
"dialInSummaryError": "Errore nella ricerca dei numeri telefonici. Riprova più tardi.",
"dialInTollFree": "Numero verde",
"genericError": "Ops, qualcosa è andato storto.",
"inviteLiveStream": "Per vedere la diretta di questo meeting, clicca su questo link: {{url}}",
"invitePhone": "ATTENZIONE E' UNA CHIAMATA INTERNAZIONALE A PAGAMENTO! NON E' GRATUITA. Per seguire solo telefonicamente, clicca: {{number}},,{{conferenceID}}#",
"invitePhoneAlternatives": "Cerchi un numero diverso da chiamare?\nEcco dei numeri telefonici per collegarsi alle riunioni: {{url}}\n\n\nSe entri in riunione anche col computer, entra senza attivare l'audio: {{silentUrl}}",
"invitePhone": "Per seguire solo telefonicamente, clicca: {{number}},,{{conferenceID}}#",
"invitePhoneAlternatives": "",
"inviteURLFirstPartGeneral": "Invito a connettersi ad una conferenza.",
"inviteURLFirstPartPersonal": "{{name}} ti sta invitando ad un meeting.\n",
"inviteURLSecondPart": "\nPartecipa al meeting:\n{{url}}\n",
@@ -366,12 +332,12 @@
"label": "Informazioni meeting"
},
"inviteDialog": {
"alertText": "Errore nell'invitare alcuni partecipanti.",
"alertText": "",
"header": "Invita",
"searchCallOnlyPlaceholder": "Inserisci numero di telefono",
"searchPeopleOnlyPlaceholder": "Cerca partecipanti",
"searchPlaceholder": "Partecipante o numero di telefono",
"send": "Invia"
"searchPeopleOnlyPlaceholder": "",
"searchPlaceholder": "",
"send": ""
},
"inlineDialogFailure": {
"msg": "Un piccolo inconveniente.",
@@ -394,11 +360,9 @@
"toggleScreensharing": "Cambia modalità tra videocamera e condivisione schermo",
"toggleShortcuts": "Mostra o nascondi le scorciatoie",
"videoMute": "Accendo o spegni la videocamera",
"videoQuality": "Imposta qualità della telefonata"
"videoQuality": ""
},
"liveStreaming": {
"limitNotificationDescriptionWeb": "Data l'alta domanda la tua diretta sarà limitata a {{limit}} minuti. Per dirette illimitate, prova <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"limitNotificationDescriptionNative": "La tua diretta sarà limitata a {{limit}} minuti. Per dirette illimitate, prova {{app}}.",
"busy": "Stiamo cercando di liberare risorse per la diretta. Riprova tra qualche minuto.",
"busyTitle": "Tutti gli streamer sono impegnati al momento",
"changeSignIn": "Cambia account",
@@ -415,9 +379,7 @@
"getStreamKeyManually": "Non siamo stati in grado di trovare nessuna trasmissione dal vivo. Prova ad ottenere una chiave stream da Youtube",
"invalidStreamKey": "La chiave stream potrebbe non essere corretta.",
"off": "La diretta si è interrotta",
"offBy": "{{name}} ha fermato la diretta",
"on": "Trasmissione in diretta",
"onBy": "{{name}} ha iniziato la diretta",
"pending": "Avvio diretta...",
"serviceName": "Servizio live streaming",
"signedInAs": "Sei attualmente collegato come:",
@@ -427,8 +389,10 @@
"start": "Inizia una diretta",
"streamIdHelp": "Cos'è questo?",
"unavailableTitle": "La diretta non è disponibile",
"youtubeTerms": "YouTube terms of services",
"googlePrivacyPolicy": "Google Privacy Policy"
"onBy": "",
"offBy": "",
"googlePrivacyPolicy": "Google Privacy Policy",
"youtubeTerms": "YouTube terms of services"
},
"localRecording": {
"clientState": {
@@ -479,84 +443,24 @@
"muted": "Hai iniziato la conversazione con l'audio disattivato.",
"mutedTitle": "Hai l'audio disattivato!",
"mutedRemotelyTitle": "Ti è stato disattivato l'audio da {{participantDisplayName}}!",
"mutedRemotelyDescription": "Puoi sempre attivare il microfono, quando vuoi parlare. Spegni il microfono quando hai finito, per non introdurre rumori di fondo nella riunione.",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) è stata tolta da un altro partecipante",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) è stata messa da un altro partecipante",
"mutedRemotelyDescription": "",
"passwordRemovedRemotely": "",
"passwordSetRemotely": "",
"raisedHand": "{{name}} vorrebbe intervenire.",
"somebody": "Qualcuno",
"startSilentTitle": "Sei entrato in riunione senza aver scelto un dispositivo audio per sentire!",
"startSilentDescription": "Entra di nuovo in riunione, per attivare l'audio",
"startSilentTitle": "",
"startSilentDescription": "",
"suboptimalExperienceDescription": "Ehm... temiamo che la tua esperienza con {{appName}} non sarà granché su questo browser. Stiamo cercando di migliorare la situazione ma, per il momento, prova ad utilizzare uno di questi <a href='{{recommendedBrowserPageLink}}' target='_blank'>browser supportati</a>.",
"suboptimalExperienceTitle": "Problemi con il browser",
"unmute": "Accendi microfono",
"unmute": "",
"newDeviceCameraTitle": "Trovata nuova videocamera",
"newDeviceAudioTitle": "Trovata nuova origine audio",
"newDeviceAction": "OK, usala",
"OldElectronAPPTitle": "Falla di sicurezza!",
"oldElectronClientDescription1": "Sembri stare usando una versione obsoleta di Jitsi Meet che ha delle falle di sicurezza note. Assicurati di aggiornarla presso il nostro ",
"oldElectronClientDescription2": "ultima build",
"oldElectronClientDescription3": " ora!"
"newDeviceAction": "Usala",
"suboptimalBrowserWarning": "Ci spiace che la tua videoconferenza non sarà ottimale, qui. Stiamo cercando modi per migliorare, ma fino ad allora per favore prova ad usare <a href='{{recommendedBrowserPageLink}}' target='_blank'>browser supportati completamente/a>."
},
"passwordSetRemotely": "definita da altro utente",
"passwordDigitsOnly": "Fino a {{number}} cifre",
"poweredby": "offerto da",
"prejoin": {
"audioAndVideoError": "Errore audio e video:",
"audioDeviceProblem": "C'è un problema con il tuo microfono",
"audioOnlyError": "Errore audio:",
"audioTrackError": "Impossibile creare traccia audio.",
"calling": "Chiamando",
"callMe": "Chiamami",
"callMeAtNumber": "Chiamami a questo numero:",
"configuringDevices": "Configurazione dispositivi...",
"connectedWithAudioQ": "Sei connesso con l'audio?",
"connection": {
"good": "La tua connessione Internet sembra buona!",
"nonOptimal": "La tua connessione Internet non è ottimale",
"poor": "La tua connessione Internet è scarsa"
},
"connectionDetails": {
"audioClipping": "Ci aspettiamo che il tuo audio sarà a sighiozzo.",
"audioHighQuality": "Ci aspettiamo che il tuo audio sarà di eccellente qualità.",
"audioLowNoVideo": "Ci aspettiamo una bassa qualità audio e che il video sia assente.",
"goodQuality": "Ottimo! La tue funzioni multimediali andranno alla grande.",
"noMediaConnectivity": "Non siamo riusciti a stabilire una connessione multimediale per fare questo test. Questo è tipicamente causato da un firewall o dal NAT.",
"noVideo": "Ci aspettiamo una pessima qualità video.",
"undetectable": "Se non riesci ancora a fare chiamate nel browser, ti consigliamo di verificare che il microfono e la tua videocamera siano configurate correttamente, che tu abbia dato al browser i permessi di usare microfono e videocamera, e che il tuo browser sia aggiornato all'ultima versione. Se hai ancora problemi, dovresti contattare lo sviluppatore dell'applicazione web.",
"veryPoorConnection": "Ci aspettiamo una qualità della chiamata davvero terribile.",
"videoFreezing": "Ci aspettiamo che il video si blocchi, sparisca, o sia molto pixelato.",
"videoHighQuality": "Ci aspettiamo che il video sia di buona qualità.",
"videoLowQuality": "Ci aspettiamo che il video abbia pochi fotogrammi al secondo e sia a bassa risoluzione.",
"videoTearing": "Ci aspettiamo che il video sia pixelato e abbia deformazioni visive."
},
"copyAndShare": "Copia e condividi il collegamento della riunione",
"dialInMeeting": "Chiama e collegati alla riunione",
"dialInPin": "Chiama e inserisci il PIN per entrare nella riunione:",
"dialing": "Chiamata",
"doNotShow": "Non mostrare più questa finestra",
"errorDialOut": "Impossibile fare la chiamata",
"errorDialOutDisconnected": "Impossibile fare la chiamata. Occupato",
"errorDialOutFailed": "Impossibile fare la chiamata. Chiamata fallita",
"errorDialOutStatus": "Errore nel ricevere lo stato della rete",
"errorMissingName": "Inserire il proprio nome, per accedere alla riunione",
"errorStatusCode": "Errore nella chiamata, codice: {{status}}",
"errorValidation": "Numero inesistente",
"iWantToDialIn": "Voglio chiamare il numero",
"joinAudioByPhone": "Collegati usando un telefono, per parlare",
"joinMeeting": "Collegati alla riunione",
"joinWithoutAudio": "Collegati senza poter parlare",
"initiated": "Chiamata avviata",
"linkCopied": "Collegamento copiato negli appunti",
"lookGood": "Sembra che il tuo microfono funzioni correttamente",
"or": "o",
"premeeting": "Attesa riunione",
"showScreen": "Avvia la schermata d'attesa della riunione",
"startWithPhone": "Avvia usando il telefono, per parlare",
"screenSharingError": "Errore di condivisione dello schermo:",
"videoOnlyError": "Errore video:",
"videoTrackError": "Impossibile creare la traccia video.",
"viewAllNumbers": "vedi tutti i numeri"
},
"presenceStatus": {
"busy": "Occupato",
"calling": "Chiamata…",
@@ -577,10 +481,7 @@
"setEmailLabel": "Imposta la mail gravatar",
"title": "Profilo"
},
"raisedHand": "Vorrebbe parlare",
"recording": {
"limitNotificationDescriptionWeb": "Data l'alta domanda la tua registrazione sarà limitata a {{limit}} minuti. Per registrazioni illimitate, prova <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"limitNotificationDescriptionNative": "La tua registrazione sarà limitata a {{limit}} minuti. Per registrazioni illimitate, prova <3>{{app}}</3>.",
"authDropboxText": "Carica su Dropbox",
"availableSpace": "Spazio disponibile: {{spaceLeft}} MB (rimangono approssimativamente {{duration}} minuti di registrazione)",
"beta": "BETA",
@@ -591,31 +492,25 @@
"expandedOn": "La registrazione della conferenza è attiva.",
"expandedPending": "La registrazione è in fase di avvio…",
"failedToStart": "Non è stato possibile avviare la registrazione",
"fileSharingdescription": "Condividi la registrazione con i partecipanti alla riunione",
"fileSharingdescription": "",
"live": "DIRETTA",
"loggedIn": "Accesso effettuato come {{userName}}",
"off": "Registrazione interrotta",
"offBy": "{{name}} registrazione fermata",
"on": "Registrazione",
"onBy": "{{name}} registrazione iniziata",
"pending": "In preparazione alla registrazione della conferenza…",
"rec": "REC",
"serviceDescription": "La tua registrazione verrà salvata dal servizio di registrazione che hai scelto",
"serviceDescription": "",
"serviceName": "Servizio di registrazione",
"signIn": "Entra",
"signOut": "Esci",
"unavailable": "Ops! Il {{serviceName}} non è al momento disponibile. Stiamo lavorando per risolvere il problema. Riprova più tardi.",
"unavailableTitle": "Registrazione non disponibile"
"unavailableTitle": "Registrazione non disponibile",
"onBy": "{{name}} registrazione iniziata",
"offBy": "{{name}} registrazione fermata"
},
"sectionList": {
"pullToRefresh": "Trascina per aggiornare"
},
"security": {
"about": "Puoi aggiungere alla riunione una $t(lockRoomPassword). I partecipanti dovranno fornire la $t(lockRoomPassword) per essere autorizzati a partecipare alla riunione.",
"aboutReadOnly": "I moderatori della riunione possono aggiungere $t(lockRoomPassword). I partecipanti dovranno fornire la $t(lockRoomPassword) per essere autorizzati a partecipare alla riunione.",
"insecureRoomNameWarning": "La riunione non è protetta. Dei partecipanti indesiderati potrebbero unirsi alla riunione. Puoi proteggere l'accesso alla riunione col bottone sicurezza.",
"securityOptions": "Impostazioni sicurezza"
},
"settings": {
"calendar": {
"about": "Lintegrazione del calendario con {{appName}} e consigliata per accedere in sicurezza al proprio calendario per poter leggere i prossimi appuntamenti ",
@@ -642,26 +537,23 @@
"microphones": "Microfoni"
},
"settingsView": {
"advanced": "Avanzate",
"alertOk": "OK",
"alertCancel": "Annulla",
"alertTitle": "Attenzione",
"alertURLText": "L'URL del server inserito non è valido",
"buildInfoSection": "Versione",
"conferenceSection": "Conferenza",
"disableCallIntegration": "Disattiva l'integrazione delle chiamate native",
"disableP2P": "Disattiva la modalità punto-punto",
"disableCrashReporting": "Disattiva la diagnostica dei crash",
"disableCrashReportingWarning": "Sei sicuro di voler disattivare la diagnostica dei crash? Quest'impostazione verrà eseguita al prossimo avvio dell'app.",
"displayName": "Nome visualizzato",
"email": "Email",
"header": "Impostazioni",
"profileSection": "Profilo",
"serverURL": "URL del server",
"showAdvanced": "Impostazioni avanzate",
"startWithAudioMuted": "Inizia con l'audio disattivato",
"startWithVideoMuted": "Avvia con il video disattivato",
"version": "Versione"
"version": "Versione",
"showAdvanced": "Mostra impostazioni avanzate",
"disableP2P": "Disabilita modalità uno-a-uno",
"disableCallIntegration": "Disabilita integrazione nativa delle chiamate",
"advanced": "Avanzate"
},
"share": {
"dialInfoText": "\n\n=====\n\nVuoi solo ascoltare la conferenza da un telefono?\n\n{{defaultDialInNumber}}Clicca questo link per vedere i numeri telefonici di questo meeting\n{{dialInfoPageUrl}}",
@@ -673,113 +565,90 @@
"minutes": "{{count}}m",
"name": "Nome",
"seconds": "{{count}}s",
"speakerStats": "Statistiche",
"speakerTime": "Tempo"
"speakerStats": "Statistiche del relatore",
"speakerTime": "Tempo del relatore"
},
"startupoverlay": {
"policyText": " ",
"genericTitle": "Per la riunione devono essere usati il tuo microfono e la tua videocamera.",
"title": "{{app}} ha bisogno di usare il tuo microfono e la tua videocamera."
"title": "{{app}} chiede di usare il tuo microfono e la tua videocamera."
},
"suspendedoverlay": {
"rejoinKeyTitle": "Ricollegati",
"text": "Premi il pulsante <i>Ricollegati</i> per ricollegarti.",
"title": "La video chiamata si è interrotta perché il computer è stato sospeso."
"title": "La video chiamata si è interrotta perchè il computer è stato sospeso."
},
"toolbar": {
"accessibilityLabel": {
"audioOnly": "Attiva/disattiva solo audio",
"audioRoute": "Scegli l'uscita audio",
"callQuality": "Imposta qualità della chiamata",
"callQuality": "Gestisci qualità della chiamata",
"cc": "Attiva/disattiva sottotitoli",
"chat": "Attiva/disattiva la chat",
"document": "Attiva/disattiva documento condiviso",
"download": "Scarica le nostre app",
"embedMeeting": "Incorpora riunione altrove",
"feedback": "Lascia un feedback",
"fullScreen": "Attiva/disattiva schermo intero",
"grantModerator": "Autorizza Moderator",
"hangup": "Lascia la conferenza",
"help": "Aiuto",
"invite": "Invita persone",
"kick": "Espelli partecipante",
"lobbyButton": "Attiva/disattiva sala d'attesa",
"localRecording": "Abilita controlli di registrazione locale",
"lockRoom": "Attiva o disattiva password",
"moreActions": "Attiva o disattiva menu avanzato",
"moreActionsMenu": "Menu avanzato",
"moreOptions": "Più opzioni",
"mute": "Attiva/disattiva audio",
"muteEveryone": "Zittisci tutti",
"pip": "Attiva/disattiva immagine nellimmagine",
"privateMessage": "Invia messaggio privato",
"profile": "Modifica profilo",
"raiseHand": "Attiva/disattiva alzata di mano",
"recording": "Attiva/disattiva registrazione",
"remoteMute": "Zittisci partecipante",
"security": "Impostazioni sicurezza",
"remoteMute": "Disattiva audio partecipante",
"Settings": "Attiva/disattiva impostazioni",
"sharedvideo": "Attiva/disattiva condivisione YouTube",
"shareRoom": "Invita qualcuno",
"shareYourScreen": "Attiva/disattiva condivisione schermo",
"shortcuts": "Attiva/disattiva scorciatoie",
"show": "Mostra in primo piano",
"show": "",
"speakerStats": "Attiva/disattiva statistiche relatore",
"tileView": "Vedi tutti i partecipanti insieme, o uno solo",
"toggleCamera": "Cambia videocamera",
"toggleFilmstrip": "Attiva/disattiva pellicola",
"videomute": "Attiva/disattiva videocamera",
"videoblur": "Attiva/disattiva offuscamento video"
"videoblur": "Attiva/disattiva offuscamento video",
"privateMessage": "Invia messaggio privato",
"muteEveryone": "Zittisci tutti",
"moreOptions": "Mostra più opzioni",
"help": "Aiuto",
"download": "Scarica altre app"
},
"addPeople": "Aggiungi persone alla chiamata",
"audioOnlyOff": "Anche video",
"audioOnlyOn": "Solo audio",
"audioRoute": "Scegli l'uscita audio",
"authenticate": "Autenticazione",
"callQuality": "Imposta qualità della chiamata",
"callQuality": "Gestisci qualità della chiamata",
"chat": "Apri / Chiudi chat",
"closeChat": "Chiudi chat",
"documentClose": "Chiudi documento condiviso",
"documentOpen": "Apri documento condiviso",
"download": "Scarica le nostre app",
"e2ee": "Crittografia punto-punto",
"embedMeeting": "Incorpora riunione altrove",
"enterFullScreen": "Visualizza a schermo intero",
"enterTileView": "Vedi tutti i partecipanti",
"exitFullScreen": "Esci da schermo intero",
"exitTileView": "Vedi una persona sola",
"feedback": "Lascia un feedback",
"hangup": "Butta giù",
"help": "Aiuto",
"hangup": "Esci",
"invite": "Invita persone",
"lobbyButtonDisable": "Disabilita sala d'attesa",
"lobbyButtonEnable": "Abilita sala d'attesa",
"login": "Login",
"logout": "Logout",
"lowerYourHand": "Abbassa la mano",
"moreActions": "Più azioni",
"moreOptions": "Più opzioni",
"mute": "Attiva / Disattiva microfono",
"muteEveryone": "Zittisci tutti",
"noAudioSignalTitle": "Non arrivano suoni dal tuo microfono!",
"noAudioSignalDesc": "Se non l'hai disabilitato intenzionalmente nelle impostazioni, prova a cambiare dispositivo di input.",
"noAudioSignalDescSuggestion": "Se non l'hai disabilitato intenzionalmente nelle impostazioni, prova a scegliere il dispositivo consigliato.",
"noAudioSignalDialInDesc": "Puoi anche chiamare usando:",
"noAudioSignalDialInLinkDesc": "Numberi di telefono",
"noisyAudioInputTitle": "Il tuo microfono sembra rumoroso!",
"noisyAudioInputDesc": "Sembra che il tuo microfono faccia dei rumori, prova a spegnerlo o cambiarlo per favore.",
"mute": "Microfono Attiva / Disattiva",
"openChat": "Apri una chat",
"pip": "Abilita visualizzazione immagine nellimmagine",
"privateMessage": "invia un messaggio privato",
"profile": "Modifica profilo",
"raiseHand": "Alza / Abbassa la mano",
"raiseYourHand": "Alza la mano",
"security": "Impostazioni sicurezza",
"Settings": "Impostazioni",
"sharedvideo": "Condividi un video Youtube",
"shareRoom": "Invita partecipante",
"shortcuts": "Visualizza scorciatoie",
"speakerStats": "Statistiche",
"speakerStats": "Statistiche dell'interlocutore",
"startScreenSharing": "Inizia la condivisione dello schermo",
"startSubtitles": "Avvia sottotitoli",
"stopScreenSharing": "Ferma la condivisione dello schermo",
@@ -828,37 +697,34 @@
},
"videoStatus": {
"audioOnly": "AUD",
"audioOnlyExpanded": "Hai attivato la modalità solo audio. Questa modalità permette di risparmiare banda, ma non vedrai gli altri partecipanti.",
"audioOnlyExpanded": "Hai attivato la modalità solo audio. Questa modalità permette di rispamiare banda, ma non vedrai gli altri partecipanti.",
"callQuality": "Qualità video",
"hd": "HD",
"hdTooltip": "Stai vedendo in alta definizione",
"highDefinition": "Alta definizione",
"labelTooiltipNoVideo": "Nessun video",
"labelTooltipAudioOnly": "Hai attivato la modalità solo audio",
"ld": "LD",
"ldTooltip": "Stai vedendo a bassa definizione",
"lowDefinition": "Bassa definizione",
"onlyAudioAvailable": "È disponibile solo l'audio",
"onlyAudioSupported": "Per questo browser è supportato solo l'audio.",
"sd": "SD",
"sdTooltip": "Stai vedendo a definizione standard",
"standardDefinition": "Definizione standard"
},
"videothumbnail": {
"domute": "Disattiva audio",
"domuteOthers": "Zittisci tutti gli altri",
"flip": "Rifletti",
"grantModerator": "Autorizza moderatore",
"kick": "Butta fuori",
"kick": "Espelli",
"moderator": "Moderatore",
"mute": "Il partecipante ha il microfono spento",
"mute": "Il partecipante è in muto",
"muted": "Audio disattivato",
"remoteControl": "Controllo remoto",
"show": "Mostra in primo piano",
"videomute": "Il partecipante ha la videocamera spenta"
"show": "",
"videomute": "Silenzia il video"
},
"welcomepage": {
"accessibilityLabel": {
"join": "Tap per accedere",
"roomname": "Inserisci nome stanza"
"roomname": "Inserisci Nome Stanza"
},
"appDescription": "Avvia una videochiamata con tutto il team. Invita tutti quelli che conosci. {{app}} è una soluzione per effettuare videoconferenze totalmente crittografata, 100% open source, che puoi usare sempre, ogni giorno, gratuitamente senza bisogno di un account.",
"audioVideoSwitch": {
@@ -867,68 +733,34 @@
},
"calendar": "Calendario",
"connectCalendarButton": "Collega calendario",
"connectCalendarText": "Connetti il tuo calendario per vedere tutte le riunione dentro {{app}}. Poi, aggiungi {{provider}} di riunioni al tuo calendario, per avviarle con un clic.",
"connectCalendarText": "",
"enterRoomTitle": "Avvia una nuova conferenza",
"getHelp": "Trova aiuto",
"go": "VAI",
"goSmall": "VAI",
"headerTitle": "Jitsi Meet",
"headerSubtitle": "Secure and high quality meetings",
"info": "Informazioni chiamata",
"join": "UNISCITI",
"jitsiOnMobile": "Jitsi su mobile scarica le nostre app e dai inizio ad una riunione dovunque tu sia",
"moderatedMessage": "O <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">prepara una URL</a> in anticipo, per le riunioni di cui sei il moderatore.",
"info": "Informazioni",
"privacy": "Privacy",
"recentList": "Recente",
"recentListDelete": "Cancella",
"recentListEmpty": "La tua lista è vuota. Chatta con qualcuno del tuo team e lo vedrai apparire nella lista di meeting recenti.",
"reducedUIText": "Benvenuto in {{app}}!",
"roomNameAllowedChars": "Il nome della riunione non deve contenere questi caratteri: ?, &, :, ', \", %, #.",
"roomname": "Inserisci nome stanza",
"reducedUIText": "",
"roomname": "Inserisci Nome Stanza",
"roomnameHint": "Inserisci il nome o l'URL della stanza alla quale vuoi accedere. Puoi anche inventarti un nome, assicurati solo che le persone che vuoi contattare lo sappiano, così che possano inserire lo stesso nome.",
"sendFeedback": "Invia feedback",
"startMeeting": "Inizia riunione",
"terms": "Termini di utilizzo",
"title": "Il sistema di videoconferenza sicuro, funzionale e completamente gratuito."
"title": "Il sistema di conferenza sicuro, funzionale e completamente gratuito."
},
"documentSharing": {
"title": ""
},
"defaultNickname": "",
"chromeExtensionBanner": {
"dontShowAgain": "",
"buttonText": "",
"installExtensionText": ""
},
"raisedHand": "Vorrebbe parlare",
"lonelyMeetingExperience": {
"button": "invita altri",
"button": "Invita altri",
"youAreAlone": "Sei l'unico in riunione"
},
"helpView": {
"header": "Aiuto"
},
"lobby": {
"knockingParticipantList": "Lista dei partecipanti in attesa",
"allow": "Autorizza",
"backToKnockModeButton": "No password, ask to join instead",
"dialogTitle": "Sala d'attesa",
"disableDialogContent": "Sala d'attesa attiva. Questa funzione ti permette di non dare accesso alla riunione a partecipanti indesiderati. Vuoi disattivarla?",
"disableDialogSubmit": "Disattiva",
"emailField": "Inserisci il tuo indirizzo Email",
"enableDialogPasswordField": "Imposta password (opzionale)",
"enableDialogSubmit": "Attiva",
"enableDialogText": "La sala d'attesa ti permette di proteggere la tua riunione concedendo l'ingresso solo alle persone autorizzate da un moderatore.",
"enterPasswordButton": "Inserisci password riunione",
"enterPasswordTitle": "Inserisci la password per entrare nella riunione",
"invalidPassword": "Password errata",
"joiningMessage": "Entrerai nella riunione, non appena qualcuno approva la tua richiesta",
"joinWithPasswordMessage": "Ho inviato la password per entrare, attendi...",
"joinRejectedMessage": "La tua richiesta d'accesso è stata respinta da un moderatore.",
"joinTitle": "Entra in riunione",
"joiningTitle": "Richiesta inviata...",
"joiningWithPasswordTitle": "Entrando con la password...",
"knockButton": "Chiedi d'entrare",
"knockTitle": "Qualcuno vuole entrare in riunione",
"nameField": "Scrivi il tuo nome",
"notificationLobbyAccessDenied": "{{targetParticipantName}} è stato respinto da {{originParticipantName}}",
"notificationLobbyAccessGranted": "{{targetParticipantName}} è stato autorizzato ad entrare da {{originParticipantName}}",
"notificationLobbyDisabled": "La sala d'attesa è stata disattivata da {{originParticipantName}}",
"notificationLobbyEnabled": "La sala d'attesa è stata attivata da {{originParticipantName}}",
"notificationTitle": "Sala d'attesa",
"passwordField": "Inserisci la password della riunione",
"passwordJoinButton": "Entra",
"reject": "Respingi",
"toggleLabel": "Attiva sala d'attesa"
}
}

View File

@@ -104,7 +104,7 @@
},
"deepLinking": {
"downloadApp": "Last ned programmet",
"openApp": "Fortsett til programmet"
"openApp": "Fortsett til programmet",
},
"defaultLink": "f.eks.",
"deviceError": {

View File

@@ -300,7 +300,6 @@
"tokenAuthFailedTitle": "Authentication failed",
"transcribing": "Transcribing",
"unlockRoom": "Remove meeting $t(lockRoomPassword)",
"user": "user",
"userPassword": "user password",
"WaitForHostMsg": "The conference <b>{{room}}</b> has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
"WaitForHostMsgWOk": "The conference <b>{{room}}</b> has not yet started. If you are the host then please press Ok to authenticate. Otherwise, please wait for the host to arrive.",
@@ -394,7 +393,8 @@
"toggleFilmstrip": "Show or hide video thumbnails",
"toggleScreensharing": "Switch between camera and screen sharing",
"toggleShortcuts": "Show or hide keyboard shortcuts",
"videoMute": "Start or stop your camera"
"videoMute": "Start or stop your camera",
"videoQuality": "Manage call quality"
},
"liveStreaming": {
"limitNotificationDescriptionWeb": "Due to high demand your streaming will be limited to {{limit}} min. For unlimited streaming try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",

View File

@@ -0,0 +1,14 @@
{
"google-auth": {
"matchPatterns": {
"url": "accounts.google.com"
},
"target": "electron"
},
"dropbox-auth": {
"matchPatterns": {
"url": "dropbox.com/oauth2/authorize"
},
"target": "electron"
}
}

View File

@@ -7,6 +7,7 @@ import {
Transport
} from '../../transport';
import electronPopupsConfig from './electronPopupsConfig.json';
import {
getAvailableDevices,
getCurrentDevices,
@@ -1069,4 +1070,16 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
stopRecording(mode) {
this.executeCommand('startRecording', mode);
}
/**
* Returns the configuration for electron for the windows that are open
* from Jitsi Meet.
*
* @returns {Promise<Object>}
*
* NOTE: For internal use only.
*/
_getElectronPopupsConfig() {
return Promise.resolve(electronPopupsConfig);
}
}

View File

@@ -17,7 +17,6 @@ function getPasswordInputHtml() {
return `
<input name="username" type="text"
class="input-control"
data-i18n="[placeholder]dialog.user"
placeholder=${placeholder} autofocus>
<input name="password" type="password"
class="input-control"

View File

@@ -218,11 +218,21 @@ export default class LargeVideoManager {
// change the avatar url on large
this.updateAvatar();
// If the user's connection is disrupted then the avatar will be
// displayed in case we have no video image cached. That is if
// there was a user switch (image is lost on stream detach) or if
// the video was not rendered, before the connection has failed.
const wasUsersImageCached
= !isUserSwitch && container.wasVideoRendered;
const isVideoMuted = !stream || stream.isMuted();
const participant = getParticipantById(APP.store.getState(), id);
const connectionStatus = participant?.connectionStatus;
const isVideoRenderable = !isVideoMuted
&& (APP.conference.isLocalId(id) || connectionStatus === JitsiParticipantConnectionStatus.ACTIVE);
const isVideoRenderable
= !isVideoMuted
&& (APP.conference.isLocalId(id)
|| connectionStatus
=== JitsiParticipantConnectionStatus.ACTIVE
|| wasUsersImageCached);
const showAvatar
= isVideoContainer

View File

@@ -13,8 +13,6 @@ import {
JitsiParticipantConnectionStatus
} from '../../../react/features/base/lib-jitsi-meet';
import { getParticipantById } from '../../../react/features/base/participants';
import { isTestModeEnabled } from '../../../react/features/base/testing';
import { updateLastTrackVideoMediaEvent } from '../../../react/features/base/tracks';
import { PresenceLabel } from '../../../react/features/presence-status';
import { stopController, requestRemoteControl } from '../../../react/features/remote-control';
import { RemoteVideoMenuTriggerButton } from '../../../react/features/remote-video-menu';
@@ -25,15 +23,6 @@ import SmallVideo from './SmallVideo';
const logger = Logger.getLogger(__filename);
/**
* List of container events that we are going to process, will be added as listener to the
* container for every event in the list. The latest event will be stored in redux.
*/
const containerEvents = [
'abort', 'canplay', 'canplaythrough', 'emptied', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart',
'pause', 'play', 'playing', 'ratechange', 'stalled', 'suspend', 'waiting'
];
/**
*
* @param {*} spanId
@@ -217,20 +206,23 @@ export default class RemoteVideo extends SmallVideo {
}
/**
* The remote video is considered "playable" once the can play event has been received.
* The remote video is considered "playable" once the can play event has been received. It will be allowed to
* display video also in {@link JitsiParticipantConnectionStatus.INTERRUPTED} if the video has received the canplay
* event and was not muted while not in ACTIVE state. This basically means that there is stalled video image cached
* that could be displayed. It's used to show "grey video image" in user's thumbnail when there are connectivity
* issues.
*
* @inheritdoc
* @override
*/
isVideoPlayable() {
const participant = getParticipantById(APP.store.getState(), this.id);
const { connectionStatus } = participant || {};
const { connectionStatus, mutedWhileDisconnected } = participant || {};
return (
super.isVideoPlayable()
&& this._canPlayEventReceived
&& connectionStatus === JitsiParticipantConnectionStatus.ACTIVE
);
return super.isVideoPlayable()
&& this._canPlayEventReceived
&& (connectionStatus === JitsiParticipantConnectionStatus.ACTIVE
|| (connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED && !mutedWhileDisconnected));
}
/**
@@ -256,8 +248,6 @@ export default class RemoteVideo extends SmallVideo {
* @param {*} stream
*/
waitForPlayback(streamElement, stream) {
$(streamElement).hide();
const webRtcStream = stream.getOriginalStream();
const isVideo = stream.isVideoTrack();
@@ -267,12 +257,7 @@ export default class RemoteVideo extends SmallVideo {
const listener = () => {
this._canPlayEventReceived = true;
logger.info(`${this.id} video is now active`, streamElement);
if (streamElement) {
$(streamElement).show();
}
this.VideoLayout.remoteVideoActive(streamElement, this.id);
streamElement.removeEventListener('canplay', listener);
// Refresh to show the video
@@ -312,6 +297,8 @@ export default class RemoteVideo extends SmallVideo {
// Put new stream element always in front
streamElement = UIUtils.prependChild(this.container, streamElement);
$(streamElement).hide();
this.waitForPlayback(streamElement, stream);
stream.attach(streamElement);
@@ -322,13 +309,6 @@ export default class RemoteVideo extends SmallVideo {
// attached we need to update the menu in order to show the volume
// slider.
this.updateRemoteVideoMenu();
} else if (isTestModeEnabled(APP.store.getState())) {
const cb = name => APP.store.dispatch(updateLastTrackVideoMediaEvent(stream, name));
containerEvents.forEach(event => {
streamElement.addEventListener(event, cb.bind(this, event));
});
}
}

View File

@@ -433,7 +433,7 @@ export default class SmallVideo {
*/
computeDisplayModeInput() {
let isScreenSharing = false;
let connectionStatus;
let connectionStatus, mutedWhileDisconnected;
const state = APP.store.getState();
const participant = getParticipantById(state, this.id);
@@ -443,6 +443,7 @@ export default class SmallVideo {
isScreenSharing = typeof track !== 'undefined' && track.videoType === 'desktop';
connectionStatus = participant.connectionStatus;
mutedWhileDisconnected = participant.mutedWhileDisconnected;
}
return {
@@ -453,6 +454,7 @@ export default class SmallVideo {
isVideoPlayable: this.isVideoPlayable(),
hasVideo: Boolean(this.selectVideoElement().length),
connectionStatus,
mutedWhileDisconnected,
canPlayEventReceived: this._canPlayEventReceived,
videoStream: Boolean(this.videoStream),
isScreenSharing,

View File

@@ -5,8 +5,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { browser } from '../../../react/features/base/lib-jitsi-meet';
import { isTestModeEnabled } from '../../../react/features/base/testing';
import { ORIENTATION, LargeVideoBackground, updateLastLargeVideoMediaEvent } from '../../../react/features/large-video';
import { ORIENTATION, LargeVideoBackground } from '../../../react/features/large-video';
import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout';
/* eslint-enable no-unused-vars */
import UIEvents from '../../../service/UI/UIEvents';
@@ -20,15 +19,6 @@ export const VIDEO_CONTAINER_TYPE = 'camera';
const FADE_DURATION_MS = 300;
/**
* List of container events that we are going to process, will be added as listener to the
* container for every event in the list. The latest event will be stored in redux.
*/
const containerEvents = [
'abort', 'canplay', 'canplaythrough', 'emptied', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart',
'pause', 'play', 'playing', 'ratechange', 'stalled', 'suspend', 'waiting'
];
/**
* Returns an array of the video dimensions, so that it keeps it's aspect
* ratio and fits available area with it's larger dimension. This method
@@ -233,6 +223,14 @@ export class VideoContainer extends LargeContainer {
this.$remotePresenceMessage = $('#remotePresenceMessage');
/**
* Indicates whether or not the video stream attached to the video
* element has started(which means that there is any image rendered
* even if the video is stalled).
* @type {boolean}
*/
this.wasVideoRendered = false;
this.$wrapper = $('#largeVideoWrapper');
/**
@@ -241,12 +239,17 @@ export class VideoContainer extends LargeContainer {
* video anyway.
*/
this.$wrapperParent = this.$wrapper.parent();
this.avatarHeight = $('#dominantSpeakerAvatarContainer').height();
this.$video[0].onplaying = function(event) {
const onPlayingCallback = function(event) {
if (typeof resizeContainer === 'function') {
resizeContainer(event);
}
};
this.wasVideoRendered = true;
}.bind(this);
this.$video[0].onplaying = onPlayingCallback;
/**
* A Set of functions to invoke when the video element resizes.
@@ -256,14 +259,6 @@ export class VideoContainer extends LargeContainer {
this._resizeListeners = new Set();
this.$video[0].onresize = this._onResize.bind(this);
if (isTestModeEnabled(APP.store.getState())) {
const cb = name => APP.store.dispatch(updateLastLargeVideoMediaEvent(name));
containerEvents.forEach(event => {
this.$video[0].addEventListener(event, cb.bind(this, event));
});
}
}
/**
@@ -478,6 +473,10 @@ export class VideoContainer extends LargeContainer {
return;
}
// The stream has changed, so the image will be lost on detach
this.wasVideoRendered = false;
// detach old stream
if (this.stream) {
this.stream.detach(this.$video[0]);

View File

@@ -1,4 +1,4 @@
/* global APP */
/* global APP, $ */
import Logger from 'jitsi-meet-logger';
@@ -313,6 +313,15 @@ const VideoLayout = {
remoteVideo.updateView();
},
// FIXME: what does this do???
remoteVideoActive(videoElement, resourceJid) {
logger.info(`${resourceJid} video is now active`, videoElement);
if (videoElement) {
$(videoElement).show();
}
this._updateLargeVideoIfDisplayed(resourceJid, true);
},
/**
* On video muted event.
*/

52
package-lock.json generated
View File

@@ -3302,9 +3302,9 @@
}
},
"@jitsi/js-utils": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.3.tgz",
"integrity": "sha512-m6mZz7R716mHP21lTKQffyM0nNFu3Fe/EHCaOVLFY/vdPsaUl9DhypJqtPIYzRUfPnmnugdaxcxrUeSZQXQzVA==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.2.tgz",
"integrity": "sha512-ls+X9tn9EemUQwPEBr7Z0UD4sjRtwcu1Bh4MUo0Hv4arp0KVzcCYCW+mofsvuZvHg8xJX12LLNVgUKi1X5XTGg==",
"requires": {
"bowser": "2.7.0",
"js-md5": "0.7.3"
@@ -6989,11 +6989,6 @@
"gud": "^1.0.0"
}
},
"cross-os": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/cross-os/-/cross-os-1.3.0.tgz",
"integrity": "sha512-9kViqCcAwlPLTeSDPlyC2FdMQ5UVPtGZUnGV8vYDcBA3olJ/hDR7H6IfrNJft2DlKONleHf8CMhD+7Uv2tBnEw=="
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -10593,8 +10588,8 @@
"integrity": "sha512-+f/4OLeqY8RAmXnonI1ffeY1DR8kMNJPhv5WMFehchf7U71cjMQVKkOz1n6asz6kfVoAqKNWJz1A/18i18AcXA=="
},
"jitsi-meet-logger": {
"version": "github:jitsi/jitsi-meet-logger#4add5bac2e4cea73a05f42b7596ee03c7f7a2567",
"from": "github:jitsi/jitsi-meet-logger#v1.0.0"
"version": "github:jitsi/jitsi-meet-logger#5ec92357570dc8f0b7ffc1528820721c84c6af8b",
"from": "github:jitsi/jitsi-meet-logger#5ec92357570dc8f0b7ffc1528820721c84c6af8b"
},
"jquery": {
"version": "3.5.1",
@@ -10776,8 +10771,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#9f65e8fab3635ff2e05a5dcff7cc16b33215b6be",
"from": "github:jitsi/lib-jitsi-meet#9f65e8fab3635ff2e05a5dcff7cc16b33215b6be",
"version": "github:jitsi/lib-jitsi-meet#d2153eb404ddadef6d5b89ae8c499fa144280531",
"from": "github:jitsi/lib-jitsi-meet#d2153eb404ddadef6d5b89ae8c499fa144280531",
"requires": {
"@jitsi/js-utils": "1.0.2",
"@jitsi/sdp-interop": "1.0.3",
@@ -10785,7 +10780,7 @@
"async": "0.9.0",
"base64-js": "1.3.1",
"current-executing-script": "0.1.3",
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#v1.0.0",
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#5ec92357570dc8f0b7ffc1528820721c84c6af8b",
"lodash.clonedeep": "4.5.0",
"lodash.debounce": "4.0.8",
"lodash.isequal": "4.5.0",
@@ -10797,24 +10792,6 @@
"webrtc-adapter": "7.5.0"
},
"dependencies": {
"@jitsi/js-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.2.tgz",
"integrity": "sha512-ls+X9tn9EemUQwPEBr7Z0UD4sjRtwcu1Bh4MUo0Hv4arp0KVzcCYCW+mofsvuZvHg8xJX12LLNVgUKi1X5XTGg==",
"requires": {
"bowser": "2.7.0",
"js-md5": "0.7.3"
}
},
"jitsi-meet-logger": {
"version": "github:jitsi/jitsi-meet-logger#4add5bac2e4cea73a05f42b7596ee03c7f7a2567",
"from": "github:jitsi/jitsi-meet-logger#v1.0.0"
},
"js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
},
"uuid": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz",
@@ -14288,12 +14265,11 @@
"integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
},
"react-native-webrtc": {
"version": "1.87.1",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.87.1.tgz",
"integrity": "sha512-XIztid40ohLUoOIDqpavskyAPzopWIjNOoC/y3AtTymt+o+W/rIHZ9Qw8JZCaIjWh2AIrcO2wtb/f1aMWSz2Zw==",
"version": "1.84.1",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.84.1.tgz",
"integrity": "sha512-ewZBgKE+YhLaivo9Wh6aiaEp8ZRvFMqblrkDl1nptQiNNH6CungoAzSOxGDnHWAxepRfiUrW5qnADrsYKmaNeQ==",
"requires": {
"base64-js": "^1.1.2",
"cross-os": "^1.3.0",
"event-target-shim": "^1.0.5",
"prop-types": "^15.5.10",
"uuid": "^3.3.2"
@@ -14307,9 +14283,9 @@
}
},
"react-native-webview": {
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-11.0.2.tgz",
"integrity": "sha512-GDyIBRbCZ2wbMUGCxA7LufSEbSoWKOzkFB8YljmAffA15tzN6ccvGEquB/hkk5KhvoYy300kwJyEmyBeG6d/AA==",
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-10.9.0.tgz",
"integrity": "sha512-zYZfmdJca/xRbwvvOfPhzL59SQC4L0W9rPWVF4zMi7BMDdCVHXVp0wKZ9KzmqxZNwadZNTxl5s0pvd6p3S34Fg==",
"requires": {
"escape-string-regexp": "2.0.0",
"invariant": "2.2.4"

View File

@@ -32,7 +32,7 @@
"@atlaskit/theme": "7.0.2",
"@atlaskit/toggle": "5.0.14",
"@atlaskit/tooltip": "12.1.13",
"@jitsi/js-utils": "1.0.3",
"@jitsi/js-utils": "1.0.2",
"@microsoft/microsoft-graph-client": "1.1.0",
"@react-native-community/async-storage": "1.3.4",
"@react-native-community/google-signin": "3.0.1",
@@ -50,13 +50,13 @@
"i18next-browser-languagedetector": "3.0.1",
"i18next-xhr-backend": "3.0.0",
"jQuery-Impromptu": "github:trentrichardson/jQuery-Impromptu#v6.0.0",
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#v1.0.0",
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#5ec92357570dc8f0b7ffc1528820721c84c6af8b",
"jquery": "3.5.1",
"jquery-contextmenu": "2.4.5",
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#9f65e8fab3635ff2e05a5dcff7cc16b33215b6be",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#d2153eb404ddadef6d5b89ae8c499fa144280531",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.19",
"moment": "2.19.4",
@@ -84,8 +84,8 @@
"react-native-svg-transformer": "0.14.3",
"react-native-url-polyfill": "1.2.0",
"react-native-watch-connectivity": "0.4.3",
"react-native-webrtc": "1.87.1",
"react-native-webview": "11.0.2",
"react-native-webrtc": "1.84.1",
"react-native-webview": "10.9.0",
"react-native-youtube-iframe": "1.2.3",
"react-redux": "7.1.0",
"react-textarea-autosize": "7.1.0",

View File

@@ -11,7 +11,7 @@ import JitsiMeetJS, {
browser,
isAnalyticsEnabled
} from '../base/lib-jitsi-meet';
import { getJitsiMeetGlobalNS, loadScript, parseURIString } from '../base/util';
import { getJitsiMeetGlobalNS, loadScript } from '../base/util';
import { AmplitudeHandler, MatomoHandler } from './handlers';
import logger from './logger';
@@ -166,8 +166,6 @@ export function initAnalytics({ getState }: { getState: Function }, handlers: Ar
} = config;
const { group, server } = state['features/base/jwt'];
const roomName = state['features/base/conference'].room;
const { locationURL = {} } = state['features/base/connection'];
const { tenant } = parseURIString(locationURL.href) || {};
const permanentProperties = {};
if (server) {
@@ -189,9 +187,6 @@ export function initAnalytics({ getState }: { getState: Function }, handlers: Ar
// Report if we are loaded in iframe
permanentProperties.inIframe = _inIframe();
// Report the tenant from the URL.
permanentProperties.tenant = tenant || '/';
// Optionally, include local deployment information based on the
// contents of window.config.deploymentInfo.
if (deploymentInfo) {

View File

@@ -37,6 +37,7 @@ import '../overlay/middleware';
import '../recent-list/middleware';
import '../recording/middleware';
import '../rejoin/middleware';
import '../remote-control/middleware';
import '../room-lock/middleware';
import '../rtcstats/middleware';
import '../subtitles/middleware';

View File

@@ -10,7 +10,6 @@ import '../noise-detection/middleware';
import '../old-client-notification/middleware';
import '../power-monitor/middleware';
import '../prejoin/middleware';
import '../remote-control/middleware';
import '../shared-video/middleware';
import '../talk-while-muted/middleware';

View File

@@ -43,6 +43,7 @@ import '../notifications/reducer';
import '../overlay/reducer';
import '../recent-list/reducer';
import '../recording/reducer';
import '../remote-control/reducer';
import '../settings/reducer';
import '../subtitles/reducer';
import '../toolbox/reducer';

View File

@@ -8,7 +8,6 @@ import '../no-audio-signal/reducer';
import '../noise-detection/reducer';
import '../power-monitor/reducer';
import '../prejoin/reducer';
import '../remote-control/reducer';
import '../screenshot-capture/reducer';
import '../shared-video/reducer';
import '../talk-while-muted/reducer';

View File

@@ -196,6 +196,17 @@ export const SET_PENDING_SUBJECT_CHANGE = 'SET_PENDING_SUBJECT_CHANGE';
*/
export const SET_ROOM = 'SET_ROOM';
/**
* The type of (redux) action, which indicates if a SIP gateway is enabled on
* the server.
*
* {
* type: SET_SIP_GATEWAY_ENABLED
* isSIPGatewayEnabled: boolean
* }
*/
export const SET_SIP_GATEWAY_ENABLED = 'SET_SIP_GATEWAY_ENABLED';
/**
* The type of (redux) action which updates the current known status of the
* moderator features for starting participants as audio or video muted.

View File

@@ -20,6 +20,7 @@ import {
SET_PASSWORD,
SET_PENDING_SUBJECT_CHANGE,
SET_ROOM,
SET_SIP_GATEWAY_ENABLED,
SET_START_MUTED_POLICY
} from './actionTypes';
import { isRoomValid } from './functions';
@@ -89,6 +90,9 @@ ReducerRegistry.register(
case SET_ROOM:
return _setRoom(state, action);
case SET_SIP_GATEWAY_ENABLED:
return _setSIPGatewayEnabled(state, action);
case SET_START_MUTED_POLICY:
return {
...state,
@@ -412,3 +416,16 @@ function _setRoom(state, action) {
});
}
/**
* Reduces a specific Redux action SET_SIP_GATEWAY_ENABLED of the feature
* base/conference.
*
* @param {Object} state - The Redux state of the feature base/conference.
* @param {Action} action - The Redux action SET_SIP_GATEWAY_ENABLED to reduce.
* @private
* @returns {Object} The new state of the feature base/conference after the
* reduction of the specified action.
*/
function _setSIPGatewayEnabled(state, action) {
return set(state, 'isSIPGatewayEnabled', action.isSIPGatewayEnabled);
}

View File

@@ -90,6 +90,7 @@ export default [
'disableRemoteMute',
'disableRtx',
'disableSimulcast',
'disableSuspendVideo',
'disableThirdPartyRequests',
'displayJids',
'doNotStoreRoom',
@@ -114,10 +115,8 @@ export default [
'fileRecordingsEnabled',
'firefox_fake_device',
'forceJVB121Ratio',
'forceTurnRelay',
'gatherStats',
'googleApiApplicationClientID',
'hideConferenceTimer',
'hiddenDomain',
'hideLobbyButton',
'hosts',
@@ -128,6 +127,7 @@ export default [
'liveStreamingEnabled',
'localRecording',
'maxFullResolutionParticipants',
'minParticipants',
'openBridgeChannel',
'opusMaxAverageBitrate',
'p2p',
@@ -140,6 +140,7 @@ export default [
'resolution',
'startAudioMuted',
'startAudioOnly',
'startBitrate',
'startScreenSharing',
'startSilent',
'startVideoMuted',

View File

@@ -59,7 +59,7 @@ export function getInviteURL(stateOrGetState: Function | Object): string {
if (inviteDomain) {
const meetingId
= state['features/base/config'].brandingRoomAlias || urlWithoutParams.pathname.replace(/\//, '');
= state['features/base/config'].brandingRoomAlias || urlWithoutParams.pathname.replace('/', '');
return `${inviteDomain}/${meetingId}`;
}

View File

@@ -68,13 +68,14 @@ function _updateLastN({ getState }) {
return;
}
let lastN = typeof config.channelLastN === 'undefined' ? -1 : config.channelLastN;
const defaultLastN = typeof config.channelLastN === 'undefined' ? -1 : config.channelLastN;
let lastN = defaultLastN;
// Apply last N limit based on the # of participants and channelLastN settings.
// Apply last N limit based on the # of participants
const limitedLastN = limitLastN(participantCount, lastNLimits);
if (limitedLastN !== undefined) {
lastN = lastN === -1 ? limitedLastN : Math.min(limitedLastN, lastN);
lastN = limitedLastN;
}
if (typeof appState !== 'undefined' && appState !== 'active') {

View File

@@ -21,6 +21,7 @@ import {
getLocalParticipant,
getNormalizedDisplayName,
getParticipantDisplayName,
figureOutMutedWhileDisconnectedStatus,
getParticipantById
} from './functions';
import logger from './logger';
@@ -219,12 +220,15 @@ export function muteRemoteParticipant(id) {
* }}
*/
export function participantConnectionStatusChanged(id, connectionStatus) {
return {
type: PARTICIPANT_UPDATED,
participant: {
connectionStatus,
id
}
return (dispatch, getState) => {
return {
type: PARTICIPANT_UPDATED,
participant: {
connectionStatus,
id,
mutedWhileDisconnected: figureOutMutedWhileDisconnectedStatus(getState(), id, connectionStatus)
}
};
};
}

View File

@@ -1,12 +1,11 @@
// @flow
import { getGravatarURL } from '@jitsi/js-utils/avatar';
import type { Store } from 'redux';
import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media';
import { toState } from '../redux';
import { getTrackByMediaTypeAndParticipant } from '../tracks';
import { getTrackByMediaTypeAndParticipant, isRemoteTrackMuted } from '../tracks';
import { createDeferred } from '../util';
import {
@@ -24,39 +23,30 @@ declare var interfaceConfig: Object;
*/
const AVATAR_QUEUE = [];
const AVATAR_CHECKED_URLS = new Map();
/* eslint-disable arrow-body-style, no-unused-vars */
/* eslint-disable arrow-body-style */
const AVATAR_CHECKER_FUNCTIONS = [
(participant, _) => {
participant => {
return participant && participant.isJigasi ? JIGASI_PARTICIPANT_ICON : null;
},
(participant, _) => {
participant => {
return participant && participant.avatarURL ? participant.avatarURL : null;
},
(participant, store) => {
if (participant && participant.email) {
// TODO: remove once libravatar has deployed their new scaled up infra. -saghul
const gravatarBaseURL
= store.getState()['features/base/config'].gravatarBaseURL ?? 'https://www.gravatar.com/avatar/';
return getGravatarURL(participant.email, gravatarBaseURL);
}
return null;
participant => {
return participant && participant.email ? getGravatarURL(participant.email) : null;
}
];
/* eslint-enable arrow-body-style, no-unused-vars */
/* eslint-enable arrow-body-style */
/**
* Resolves the first loadable avatar URL for a participant.
*
* @param {Object} participant - The participant to resolve avatars for.
* @param {Store} store - Redux store.
* @returns {Promise}
*/
export function getFirstLoadableAvatarUrl(participant: Object, store: Store<any, any>) {
export function getFirstLoadableAvatarUrl(participant: Object) {
const deferred = createDeferred();
const fullPromise = deferred.promise
.then(() => _getFirstLoadableAvatarUrl(participant, store))
.then(() => _getFirstLoadableAvatarUrl(participant))
.then(src => {
if (AVATAR_QUEUE.length) {
@@ -369,16 +359,54 @@ export function shouldRenderParticipantVideo(stateful: Object | Function, id: st
return participantIsInLargeVideoWithScreen;
}
/**
* Figures out the value of mutedWhileDisconnected status by taking into
* account remote participant's network connectivity and video muted status.
* The flag is set to <tt>true</tt> if remote participant's video gets muted
* during his media connection disruption. This is to prevent black video
* being render on the thumbnail, because even though once the video has
* been played the image usually remains on the video element it seems that
* after longer period of the video element being hidden this image can be
* lost.
*
* @param {Object|Function} stateful - Object or function that can be resolved
* to the Redux state.
* @param {string} participantID - The ID of the participant.
* @param {string} [connectionStatus] - A connection status to be used.
* @returns {boolean} - The mutedWhileDisconnected value.
*/
export function figureOutMutedWhileDisconnectedStatus(
stateful: Function | Object, participantID: string, connectionStatus: ?string) {
const state = toState(stateful);
const participant = getParticipantById(state, participantID);
if (!participant || participant.local) {
return undefined;
}
const isActive = (connectionStatus || participant.connectionStatus) === JitsiParticipantConnectionStatus.ACTIVE;
const isVideoMuted = isRemoteTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO, participantID);
let mutedWhileDisconnected = participant.mutedWhileDisconnected || false;
if (!isActive && isVideoMuted) {
mutedWhileDisconnected = true;
} else if (isActive && !isVideoMuted) {
mutedWhileDisconnected = false;
}
return mutedWhileDisconnected;
}
/**
* Resolves the first loadable avatar URL for a participant.
*
* @param {Object} participant - The participant to resolve avatars for.
* @param {Store} store - Redux store.
* @returns {?string}
*/
async function _getFirstLoadableAvatarUrl(participant, store) {
async function _getFirstLoadableAvatarUrl(participant) {
for (let i = 0; i < AVATAR_CHECKER_FUNCTIONS.length; i++) {
const url = AVATAR_CHECKER_FUNCTIONS[i](participant, store);
const url = AVATAR_CHECKER_FUNCTIONS[i](participant);
if (url) {
if (AVATAR_CHECKED_URLS.has(url)) {

View File

@@ -12,6 +12,7 @@ import {
import { JitsiConferenceEvents } from '../lib-jitsi-meet';
import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
import { playSound, registerSound, unregisterSound } from '../sounds';
import { getTrackByJitsiTrack, TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from '../tracks';
import {
DOMINANT_SPEAKER_CHANGED,
@@ -41,7 +42,8 @@ import {
getLocalParticipant,
getParticipantById,
getParticipantCount,
getParticipantDisplayName
getParticipantDisplayName,
figureOutMutedWhileDisconnectedStatus
} from './functions';
import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds';
@@ -71,24 +73,16 @@ MiddlewareRegistry.register(store => next => action => {
break;
case DOMINANT_SPEAKER_CHANGED: {
// Ensure the raised hand state is cleared for the dominant speaker
// and only if it was set when this is the local participant
// Ensure the raised hand state is cleared for the dominant speaker.
const { conference, id } = action.participant;
const participant = getLocalParticipant(store.getState());
const isLocal = participant && participant.id === id;
if (isLocal && participant.raisedHand === undefined) {
// if local was undefined, let's leave it like that
// avoids sending unnecessary presence updates
break;
}
participant
&& store.dispatch(participantUpdated({
conference,
id,
local: isLocal,
local: participant.id === id,
raisedHand: false
}));
@@ -143,6 +137,10 @@ MiddlewareRegistry.register(store => next => action => {
case PARTICIPANT_UPDATED:
return _participantJoinedOrUpdated(store, next, action);
case TRACK_ADDED:
case TRACK_REMOVED:
case TRACK_UPDATED:
return _trackChanged(store, next, action);
}
return next(action);
@@ -374,8 +372,7 @@ function _maybePlaySounds({ getState, dispatch }, action) {
* @private
* @returns {Object} The value returned by {@code next(action)}.
*/
function _participantJoinedOrUpdated(store, next, action) {
const { dispatch, getState } = store;
function _participantJoinedOrUpdated({ dispatch, getState }, next, action) {
const { participant: { avatarURL, e2eeEnabled, email, id, local, name, raisedHand } } = action;
// Send an external update of the local participant's raised hand state
@@ -384,10 +381,10 @@ function _participantJoinedOrUpdated(store, next, action) {
if (local) {
const { conference } = getState()['features/base/conference'];
// Send raisedHand signalling only if there is a change
if (conference && raisedHand !== getLocalParticipant(getState()).raisedHand) {
conference.setLocalParticipantProperty('raisedHand', raisedHand);
}
conference
&& conference.setLocalParticipantProperty(
'raisedHand',
raisedHand);
}
}
@@ -411,7 +408,7 @@ function _participantJoinedOrUpdated(store, next, action) {
const participantId = !id && local ? getLocalParticipant(getState()).id : id;
const updatedParticipant = getParticipantById(getState(), participantId);
getFirstLoadableAvatarUrl(updatedParticipant, store)
getFirstLoadableAvatarUrl(updatedParticipant)
.then(url => {
dispatch(setLoadableAvatarUrl(participantId, url));
});
@@ -469,6 +466,55 @@ function _registerSounds({ dispatch }) {
dispatch(registerSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_FILE));
}
/**
* Notifies the feature base/participants that the action there has been a change in the tracks of the participants.
*
* @param {Store} store - The redux store in which the specified {@code action} is being dispatched.
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the specified {@code action} in the
* specified {@code store}.
* @param {Action} action - The redux action {@code PARTICIPANT_JOINED} or {@code PARTICIPANT_UPDATED} which is being
* dispatched in the specified {@code store}.
* @private
* @returns {Object} The value returned by {@code next(action)}.
*/
function _trackChanged({ dispatch, getState }, next, action) {
const { jitsiTrack } = action.track;
let track;
if (action.type === TRACK_REMOVED) {
track = getTrackByJitsiTrack(getState()['features/base/tracks'], jitsiTrack);
}
const result = next(action);
if (action.type !== TRACK_REMOVED) {
track = getTrackByJitsiTrack(getState()['features/base/tracks'], jitsiTrack);
}
if (typeof track === 'undefined' || track.local) {
return result;
}
const { participantId } = track;
const state = getState();
const participant = getParticipantById(state, participantId);
if (!participant) {
return result;
}
const mutedWhileDisconnected = figureOutMutedWhileDisconnectedStatus(state, participantId);
if (participant.mutedWhileDisconnected !== mutedWhileDisconnected) {
dispatch(participantUpdated({
id: participantId,
mutedWhileDisconnected
}));
}
return result;
}
/**
* Unregisters sounds related with the participants feature.
*

View File

@@ -221,6 +221,7 @@ function _participantJoined({ participant }) {
isJigasi,
loadableAvatarUrl,
local: local || false,
mutedWhileDisconnected: local ? undefined : false,
name,
pinned: pinned || false,
presence,

View File

@@ -1,8 +1,5 @@
// @flow
import { MEDIA_TYPE } from '../media';
import { getTrackByMediaTypeAndParticipant } from '../tracks';
/**
* Indicates whether the test mode is enabled. When it's enabled
* {@link TestHint} and other components from the testing package will be
@@ -16,43 +13,3 @@ export function isTestModeEnabled(state: Object): boolean {
return Boolean(testingConfig && testingConfig.testMode);
}
/**
* Returns the video type of the remote participant's video.
*
* @param {Store} store - The redux store.
* @param {string} id - The participant ID for the remote video.
* @returns {MEDIA_TYPE}
*/
export function getRemoteVideoType({ getState }: Object, id: String): boolean {
return getTrackByMediaTypeAndParticipant(getState()['features/base/tracks'], MEDIA_TYPE.VIDEO, id)?.videoType;
}
/**
* Returns whether the last media event received for large video indicates that the video is playing, if not muted.
*
* @param {Store} store - The redux store.
* @returns {boolean}
*/
export function isLargeVideoReceived({ getState }: Object): boolean {
const largeVideoParticipantId = getState()['features/large-video'].participantId;
const videoTrack = getTrackByMediaTypeAndParticipant(
getState()['features/base/tracks'], MEDIA_TYPE.VIDEO, largeVideoParticipantId);
const lastMediaEvent = getState()['features/large-video'].lastMediaEvent;
return videoTrack && !videoTrack.muted && (lastMediaEvent === 'playing' || lastMediaEvent === 'canplaythrough');
}
/**
* Returns whether the last media event received for a remote video indicates that the video is playing, if not muted.
*
* @param {Store} store - The redux store.
* @param {string} id - The participant ID for the remote video.
* @returns {boolean}
*/
export function isRemoteVideoReceived({ getState }: Object, id: String): boolean {
const videoTrack = getTrackByMediaTypeAndParticipant(getState()['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
const lastMediaEvent = videoTrack.lastMediaEvent;
return !videoTrack.muted && (lastMediaEvent === 'playing' || lastMediaEvent === 'canplaythrough');
}

View File

@@ -1,18 +1,10 @@
// @flow
import { CONFERENCE_WILL_JOIN } from '../conference';
import { SET_CONFIG } from '../config';
import { JitsiConferenceEvents } from '../lib-jitsi-meet';
import { MiddlewareRegistry } from '../redux';
import { getJitsiMeetGlobalNS } from '../util';
import { setConnectionState } from './actions';
import {
getRemoteVideoType,
isLargeVideoReceived,
isRemoteVideoReceived,
isTestModeEnabled
} from './functions';
import logger from './logger';
/**
@@ -27,13 +19,6 @@ MiddlewareRegistry.register(store => next => action => {
case CONFERENCE_WILL_JOIN:
_bindConferenceConnectionListener(action.conference, store);
break;
case SET_CONFIG: {
const result = next(action);
_bindTortureHelpers(store);
return result;
}
}
return next(action);
@@ -67,29 +52,6 @@ function _bindConferenceConnectionListener(conference, { dispatch }) {
null, JitsiConferenceEvents.CONNECTION_INTERRUPTED, dispatch));
}
/**
* Binds all the helper functions needed by torture.
*
* @param {Store} store - The redux store.
* @private
* @returns {void}
*/
function _bindTortureHelpers(store) {
const { getState } = store;
// We bind helpers only if testing mode is enabled
if (!isTestModeEnabled(getState())) {
return;
}
// All torture helper methods go in here
getJitsiMeetGlobalNS().testing = {
getRemoteVideoType: getRemoteVideoType.bind(null, store),
isLargeVideoReceived: isLargeVideoReceived.bind(null, store),
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store)
};
}
/**
* The handler function for conference connection events which wil store the
* latest even name in the Redux store of feature testing.

View File

@@ -104,14 +104,3 @@ export const TRACK_UPDATED = 'TRACK_UPDATED';
* }
*/
export const TRACK_WILL_CREATE = 'TRACK_WILL_CREATE';
/**
* Action to update the redux store with the current media event name of the video track.
*
* @returns {{
* type: TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
* track: Track,
* name: string
* }}
*/
export const TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT = 'TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT';

View File

@@ -23,8 +23,7 @@ import {
TRACK_NO_DATA_FROM_SOURCE,
TRACK_REMOVED,
TRACK_UPDATED,
TRACK_WILL_CREATE,
TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT
TRACK_WILL_CREATE
} from './actionTypes';
import {
createLocalTracksF,
@@ -705,22 +704,3 @@ export function setNoSrcDataNotificationUid(uid) {
uid
};
}
/**
* Updates the last media event received for a video track.
*
* @param {JitsiRemoteTrack} track - JitsiTrack instance.
* @param {string} name - The current media event name for the video.
* @returns {{
* type: TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
* track: Track,
* name: string
* }}
*/
export function updateLastTrackVideoMediaEvent(track, name) {
return {
type: TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
track,
name
};
}

View File

@@ -8,7 +8,6 @@ import {
TRACK_CREATE_ERROR,
TRACK_NO_DATA_FROM_SOURCE,
TRACK_REMOVED,
TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
TRACK_UPDATED,
TRACK_WILL_CREATE
} from './actionTypes';
@@ -41,7 +40,6 @@ import {
* @param {Track|undefined} state - Track to be modified.
* @param {Object} action - Action object.
* @param {string} action.type - Type of action.
* @param {string} action.name - Name of last media event.
* @param {string} action.newValue - New participant ID value (in this
* particular case).
* @param {string} action.oldValue - Old participant ID value (in this
@@ -79,20 +77,6 @@ function track(state, action) {
}
break;
}
case TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT: {
const t = action.track;
if (state.jitsiTrack === t) {
if (state.lastMediaEvent !== action.name) {
return {
...state,
lastMediaEvent: action.name
};
}
}
break;
}
case TRACK_NO_DATA_FROM_SOURCE: {
const t = action.track;
@@ -120,7 +104,6 @@ ReducerRegistry.register('features/base/tracks', (state = [], action) => {
switch (action.type) {
case PARTICIPANT_ID_CHANGED:
case TRACK_NO_DATA_FROM_SOURCE:
case TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT:
case TRACK_UPDATED:
return state.map(t => track(t, action));

View File

@@ -363,11 +363,6 @@ export function parseURIString(uri: ?string) {
}
obj.room = room;
if (contextRootEndIndex > 1) {
// The part of the pathname from the beginning to the room name is the tenant.
obj.tenant = pathname.substring(1, contextRootEndIndex);
}
return obj;
}
@@ -519,7 +514,7 @@ export function urlObjectToString(o: Object): ?string {
// pathname
// Web's ExternalAPI roomName
const room = o.roomName || o.room;
const room = _fixRoom(o.roomName || o.room);
if (room
&& (url.pathname.endsWith('/')

View File

@@ -96,8 +96,7 @@ class NavigationBar extends Component<Props> {
*/
function _mapStateToProps(state) {
return {
_conferenceTimerEnabled:
getFeatureFlag(state, CONFERENCE_TIMER_ENABLED, true) && !state['features/base/config'].hideConferenceTimer,
_conferenceTimerEnabled: getFeatureFlag(state, CONFERENCE_TIMER_ENABLED, true),
_meetingName: getConferenceName(state),
_meetingNameEnabled: getFeatureFlag(state, MEETING_NAME_ENABLED, true),
_visible: isToolboxVisible(state)

View File

@@ -16,12 +16,7 @@ import ParticipantsCount from './ParticipantsCount';
type Props = {
/**
* Whether the conference timer should be shown or not.
*/
_hideConferenceTimer: Boolean,
/**
* Whether the participant count should be shown or not.
* Whether then participant count should be shown or not.
*/
_showParticipantCount: boolean,
@@ -51,13 +46,13 @@ class Subject extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const { _hideConferenceTimer, _showParticipantCount, _subject, _visible } = this.props;
const { _showParticipantCount, _subject, _visible } = this.props;
return (
<div className = { `subject ${_visible ? 'visible' : ''}` }>
<span className = 'subject-text'>{ _subject }</span>
{ _showParticipantCount && <ParticipantsCount /> }
{ !_hideConferenceTimer && <ConferenceTimer /> }
<ConferenceTimer />
</div>
);
}
@@ -70,8 +65,6 @@ class Subject extends Component<Props> {
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _hideConferenceTimer: boolean,
* _showParticipantCount: boolean,
* _subject: string,
* _visible: boolean
* }}
@@ -80,7 +73,6 @@ function _mapStateToProps(state) {
const participantCount = getParticipantCount(state);
return {
_hideConferenceTimer: Boolean(state['features/base/config'].hideConferenceTimer),
_showParticipantCount: participantCount > 2,
_subject: getConferenceName(state),
_visible: isToolboxVisible(state) && participantCount > 1

View File

@@ -84,12 +84,6 @@ type Props = AbstractProps & {
*/
dispatch: Dispatch<any>,
/**
* Whether or not should display the "Save Logs" link in the local video
* stats table.
*/
enableSaveLogs: boolean,
/**
* Whether or not clicking the indicator should display a popover for more
* details.
@@ -392,7 +386,6 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
codec = { codec }
connectionSummary = { this._getConnectionStatusTip() }
e2eRtt = { e2eRtt }
enableSaveLogs = { this.props.enableSaveLogs }
framerate = { framerate }
isLocalVideo = { this.props.isLocalVideo }
maxEnabledResolution = { maxEnabledResolution }
@@ -447,8 +440,7 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
const participant
= typeof participantId === 'undefined' ? getLocalParticipant(state) : getParticipantById(state, participantId);
const props = {
_connectionStatus: participant?.connectionStatus,
enableSaveLogs: state['features/base/config'].enableSaveLogs
_connectionStatus: participant?.connectionStatus
};
if (conference) {

View File

@@ -54,11 +54,6 @@ type Props = {
*/
e2eRtt: number,
/**
* Whether or not should display the "Save Logs" link.
*/
enableSaveLogs: boolean,
/**
* The endpoint id of this client.
*/
@@ -158,13 +153,13 @@ class ConnectionStatsTable extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const { isLocalVideo, enableSaveLogs } = this.props;
const { isLocalVideo } = this.props;
return (
<div className = 'connection-info'>
{ this._renderStatistics() }
<div className = 'connection-actions'>
{ isLocalVideo && enableSaveLogs ? this._renderSaveLogs() : null}
{ isLocalVideo ? this._renderSaveLogs() : null}
{ this._renderShowMoreLink() }
</div>
{ this.props.shouldShowMore ? this._renderAdditionalStats() : null }

View File

@@ -17,32 +17,23 @@ const TILE_VIEW_SIDE_MARGINS = 10 * 2;
* @param {Object} windowSize - The size of the window.
* @param {boolean} isChatOpen - Whether the chat panel is displayed, in
* order to properly compute the tile view size.
* @param {boolean} isToolboxVisible - Whether the toolbox is visible, in order
* to adjust the available size.
* @returns {{
* type: SET_TILE_VIEW_DIMENSIONS,
* dimensions: Object
* }}
*/
export function setTileViewDimensions(
dimensions: Object, windowSize: Object, isChatOpen: boolean, isToolboxVisible: boolean) {
export function setTileViewDimensions(dimensions: Object, windowSize: Object, isChatOpen: boolean) {
const { clientWidth, clientHeight } = windowSize;
let heightToUse = clientHeight;
let widthToUse = clientWidth;
if (isChatOpen) {
widthToUse -= CHAT_SIZE;
}
if (isToolboxVisible) {
// The distance from the top and bottom of the screen, to avoid overlapping UI elements.
heightToUse -= 150;
}
const thumbnailSize = calculateThumbnailSizeForTileView({
...dimensions,
clientWidth: widthToUse,
clientHeight: heightToUse
clientHeight
});
const filmstripWidth = dimensions.columns * (TILE_VIEW_SIDE_MARGINS + thumbnailSize.width);

View File

@@ -94,13 +94,17 @@ export function calculateThumbnailSizeForTileView({
clientWidth,
clientHeight
}: Object) {
// The distance from the top and bottom of the screen, as set by CSS, to
// avoid overlapping UI elements.
const topBottomPadding = 200;
// Minimum space to keep between the sides of the tiles and the sides
// of the window.
const sideMargins = 30 * 2;
const verticalMargins = visibleRows * 10;
const viewWidth = clientWidth - sideMargins;
const viewHeight = clientHeight - verticalMargins;
const viewHeight = clientHeight - topBottomPadding - verticalMargins;
const initialWidth = viewWidth / columns;
const aspectRatioHeight = initialWidth / TILE_ASPECT_RATIO;
const height = Math.floor(Math.min(aspectRatioHeight, viewHeight / visibleRows));

View File

@@ -30,7 +30,6 @@ MiddlewareRegistry.register(store => next => action => {
const { gridDimensions } = state['features/filmstrip'].tileViewDimensions;
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { isOpen } = state['features/chat'];
const { visible } = state['features/toolbox'];
store.dispatch(
setTileViewDimensions(
@@ -39,8 +38,7 @@ MiddlewareRegistry.register(store => next => action => {
clientHeight,
clientWidth
},
isOpen,
visible
isOpen
)
);
break;

View File

@@ -18,12 +18,10 @@ StateListenerRegistry.register(
if (shouldDisplayTileView(state)) {
const gridDimensions = getTileViewGridDimensions(state);
const oldGridDimensions = state['features/filmstrip'].tileViewDimensions.gridDimensions;
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { isOpen } = state['features/chat'];
if (!equals(gridDimensions, oldGridDimensions)) {
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { isOpen } = state['features/chat'];
const { visible } = state['features/toolbox'];
store.dispatch(
setTileViewDimensions(
gridDimensions,
@@ -31,8 +29,7 @@ StateListenerRegistry.register(
clientHeight,
clientWidth
},
isOpen,
visible
isOpen
)
);
}
@@ -51,7 +48,6 @@ StateListenerRegistry.register(
case LAYOUTS.TILE_VIEW: {
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { isOpen } = state['features/chat'];
const { visible } = state['features/toolbox'];
store.dispatch(
setTileViewDimensions(
@@ -60,8 +56,7 @@ StateListenerRegistry.register(
clientHeight,
clientWidth
},
isOpen,
visible
isOpen
)
);
break;
@@ -114,7 +109,6 @@ StateListenerRegistry.register(
if (shouldDisplayTileView(state)) {
const gridDimensions = getTileViewGridDimensions(state);
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { visible } = state['features/toolbox'];
store.dispatch(
setTileViewDimensions(
@@ -123,35 +117,7 @@ StateListenerRegistry.register(
clientHeight,
clientWidth
},
isChatOpen,
visible
)
);
}
});
/**
* Listens for changes in the chat state to calculate the dimensions of the tile view grid and the tiles.
*/
StateListenerRegistry.register(
/* selector */ state => state['features/toolbox'].visible,
/* listener */ (visible, store) => {
const state = store.getState();
if (shouldDisplayTileView(state)) {
const gridDimensions = getTileViewGridDimensions(state);
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
const { isOpen } = state['features/chat'];
store.dispatch(
setTileViewDimensions(
gridDimensions,
{
clientHeight,
clientWidth
},
isOpen,
visible
isChatOpen
)
);
}

View File

@@ -19,14 +19,3 @@ export const SELECT_LARGE_VIDEO_PARTICIPANT
*/
export const UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
= 'UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION';
/**
* Action to update the redux store with the current media event name of large video.
*
* @returns {{
* type: UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT,
* name: string
* }}
*/
export const UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT
= 'UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT';

View File

@@ -61,20 +61,8 @@ export function selectParticipantInLargeVideo(participant: ?string) {
const state = getState();
const participantId = participant ?? _electParticipantInLargeVideo(state);
const largeVideo = state['features/large-video'];
const screenShares = state['features/video-layout'].screenShares;
let latestScreenshareParticipantId;
if (screenShares && screenShares.length) {
latestScreenshareParticipantId = screenShares[screenShares.length - 1];
}
// When trying to auto pin screenshare, always select the endpoint even though it happens to be
// the large video participant in redux (for the reasons listed above in the large video selection
// logic above). The auto pin screenshare logic kicks in after the track is added
// (which updates the large video participant and selects all endpoints because of the auto tile
// view mode). If the screenshare endpoint is not among the forwarded endpoints from the bridge,
// it needs to be selected again at this point.
if (participantId !== largeVideo.participantId || participantId === latestScreenshareParticipantId) {
if (participantId !== largeVideo.participantId) {
dispatch({
type: SELECT_LARGE_VIDEO_PARTICIPANT,
participantId

View File

@@ -6,8 +6,6 @@ import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
import { MEDIA_TYPE } from '../base/media';
import { getTrackByMediaTypeAndParticipant } from '../base/tracks';
import { UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT } from './actionTypes';
export * from './actions.any';
/**
@@ -85,19 +83,3 @@ export function resizeLargeVideo(width: number, height: number) {
}
};
}
/**
* Updates the last media event received for the large video.
*
* @param {string} name - The current media event name for the video.
* @returns {{
* type: UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT,
* name: string
* }}
*/
export function updateLastLargeVideoMediaEvent(name: String) {
return {
type: UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT,
name
};
}

View File

@@ -5,7 +5,7 @@ import { ReducerRegistry } from '../base/redux';
import {
SELECT_LARGE_VIDEO_PARTICIPANT,
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION, UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
} from './actionTypes';
ReducerRegistry.register('features/large-video', (state = {}, action) => {
@@ -36,13 +36,6 @@ ReducerRegistry.register('features/large-video', (state = {}, action) => {
...state,
resolution: action.resolution
};
case UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT:
return {
...state,
lastMediaEvent: action.name
};
}
return state;

View File

@@ -144,13 +144,12 @@ function _conferenceJoined({ dispatch }, next, action) {
* @param {Object} participant - The knocking participant.
* @returns {void}
*/
function _findLoadableAvatarForKnockingParticipant(store, { id }) {
const { dispatch, getState } = store;
function _findLoadableAvatarForKnockingParticipant({ dispatch, getState }, { id }) {
const updatedParticipant = getState()['features/lobby'].knockingParticipants.find(p => p.id === id);
const { disableThirdPartyRequests } = getState()['features/base/config'];
if (!disableThirdPartyRequests && updatedParticipant && !updatedParticipant.loadableAvatarUrl) {
getFirstLoadableAvatarUrl(updatedParticipant, store).then(loadableAvatarUrl => {
getFirstLoadableAvatarUrl(updatedParticipant).then(loadableAvatarUrl => {
if (loadableAvatarUrl) {
dispatch(participantIsKnockingOrUpdated({
loadableAvatarUrl,

View File

@@ -275,7 +275,7 @@ class Toolbox extends Component<Props, State> {
this._shouldShowButton('videoquality') && {
character: 'A',
exec: this._onShortcutToggleVideoQuality,
helpDescription: 'toolbar.callQuality'
helpDescription: 'keyboardShortcuts.videoQuality'
},
this._shouldShowButton('chat') && {
character: 'C',

View File

@@ -227,7 +227,7 @@ end
-- everything.
function is_feature_allowed(session, feature)
if (session.jitsi_meet_context_features == nil
or session.jitsi_meet_context_features[feature] == "true" or session.jitsi_meet_context_features[feature] == true) then
or session.jitsi_meet_context_features[feature] == "true") then
return true;
else
return false;

View File

@@ -7,7 +7,7 @@
<body>
<div class="error_page">
<h2>404 Not Found</h2>
<p class="error_page__message">You can create a new conversation <a class="link" onclick="window.location = window.location.protocol + '//' + window.location.hostname">here</a></p>
<p class="error_page__message">You can create new conversation <a class="link" href="/">here</a></p>
</div>
</body>
</html>