Compare commits

..

20 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
d69584ce17 fixup! 2021-03-16 22:20:28 +01:00
Saúl Ibarra Corretgé
b2d271a679 fix(android) update manifest override after GAP update 2021-03-16 22:10:49 +01:00
Saúl Ibarra Corretgé
7046785ca3 chore(deps) update react-native-webrtc to 1.89.1 2021-03-16 19:57:26 +01:00
Saúl Ibarra Corretgé
b817bd19d5 chore(deps) bump js-utils to 1.0.6
Fixes a harmless but confusing error in postis processing when using the
Bitwarden Chrome extension, for example.
2021-03-16 19:57:03 +01:00
luz paz
817d54b0b9 fix(misc) typos
Found via `codespell -q 3 -S ./lang`
2021-03-16 16:12:12 +01:00
Vlad Piersec
3f0bb6818c fix(toolbox): Fix always on top toolbar 2021-03-16 16:07:49 +01:00
Saúl Ibarra Corretgé
4fa47c8070 fix(virtual-background) use a DOM element for storing the image
THis will reuse the previously cached image and obey the base href.

Ref:
https://stackoverflow.com/questions/6241716/is-there-a-difference-between-new-image-and-document-createelementimg
2021-03-16 11:27:27 +01:00
Saúl Ibarra Corretgé
0dcb8a025b fix(rn,bottomsheet) limit width 2021-03-16 11:19:52 +01:00
tmoldovan8x8
8defaa9aec feat(android): adds timer to OngoingNotification 2021-03-16 12:13:37 +02:00
Vlad Piersec
d214079148 fix(toolbox): Constrain toolbox width on large mobile device 2021-03-16 09:50:49 +01:00
Vlad Piersec
096ee3cb53 fix(toolbox): Background of disabled settings button & tileview button 2021-03-16 09:20:02 +01:00
Vlad Piersec
fd606896b8 fix(toolbox): Fix buttons size in minified mode 2021-03-16 09:32:36 +02:00
Jonathan Lennox
226581a81a Add muteAudio function to load test JS. (#8802) 2021-03-15 15:37:58 -04:00
Avram Tudor
e1c5b1e626 Merge pull request #8799 from jitsi/tavram/billing-id
fix(vpaas) send jitsiMeetId instead of billingId
2021-03-15 15:54:57 +02:00
Tudor-Ovidiu Avram
831c5ba59d fix(vpaas) send jitsiMeetId instead of billingId 2021-03-15 13:22:17 +02:00
Andrei Gavrilescu
bad1bc91cf fix(screenshare): audio screen share muted state (#8785)
* AudioMixerEffect muted state

* update lib-jitsi-meet
2021-03-15 11:44:03 +02:00
Saúl Ibarra Corretgé
30d0aabaca feat(build,rnnoise) don't use an external bundle for the effect
The majority of the code is in the WASM file, the JS is just 9KB.
It's so little, in fact, that the performance hint for the main bundle didn't
have to be adjusted.
2021-03-12 23:00:50 +01:00
Saúl Ibarra Corretgé
22b6d32174 feat(build,virtual-background) don't use an external bundle for the effect
The majority of the code is in the WASM file and models, this is just a few KB.
It's so little, in fact, that the performance hint for the main bundle didn't
have to be adjusted.
2021-03-12 23:00:50 +01:00
Saúl Ibarra Corretgé
31ace267ce fix(virtual-background) use tighter edge smoothing 2021-03-12 15:05:20 +01:00
tudordan7
194d357005 feat(virtual-backgrounds) add virtual background support 2021-03-12 15:05:20 +01:00
78 changed files with 601 additions and 423 deletions

View File

@@ -6,7 +6,7 @@ build/*
flow-typed/*
libs/*
resources/*
react/features/stream-effects/blur/vendor/*
react/features/stream-effects/virtual-background/vendor/*
# ESLint will by default ignore its own configuration file. However, there does
# not seem to be a reason why we will want to risk being inconsistent with our

View File

@@ -5,8 +5,8 @@ LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
LIBFLAC_DIR = node_modules/libflacjs/dist/min/
OLM_DIR = node_modules/olm
RNNOISE_WASM_DIR = node_modules/rnnoise-wasm/dist/
TFLITE_WASM = react/features/stream-effects/blur/vendor/tflite
MEET_MODELS_DIR = react/features/stream-effects/blur/vendor/models/
TFLITE_WASM = react/features/stream-effects/virtual-background/vendor/tflite
MEET_MODELS_DIR = react/features/stream-effects/virtual-background/vendor/models/
NODE_SASS = ./node_modules/.bin/sass
NPM = npm
OUTPUT_DIR = .
@@ -51,10 +51,6 @@ deploy-appbundle:
$(OUTPUT_DIR)/analytics-ga.js \
$(BUILD_DIR)/analytics-ga.min.js \
$(BUILD_DIR)/analytics-ga.min.map \
$(BUILD_DIR)/video-blur-effect.min.js \
$(BUILD_DIR)/video-blur-effect.min.map \
$(BUILD_DIR)/rnnoise-processor.min.js \
$(BUILD_DIR)/rnnoise-processor.min.map \
$(BUILD_DIR)/close3.min.js \
$(BUILD_DIR)/close3.min.map \
$(DEPLOY_DIR)

View File

@@ -122,13 +122,17 @@ gradle.projectsEvaluated {
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
output.getProcessManifestProvider().get().doLast {
def outputDir = manifestOutputDirectory.get().asFile
def manifestPath = new File(outputDir, 'AndroidManifest.xml')
def charset = 'UTF-8'
def text
text = manifestPath.getText(charset)
def outputDir = manifestOutputDirectory
File directory
if (outputDir instanceof File) {
directory = outputDir
} else {
directory = outputDir.get().asFile
}
String manifestPath = directory.toString() + "/AndroidManifest.xml"
def text = file(manifestPath).getText('UTF-8')
text = text.replace('</application>', "${dropboxActivity}</application>")
manifestPath.write(text, charset)
file(manifestPath).write(text, 'UTF-8')
}
}
}

View File

@@ -132,6 +132,7 @@ public class JitsiMeetOngoingConferenceService extends Service
public void onCurrentConferenceChanged(String conferenceUrl) {
if (conferenceUrl == null) {
stopSelf();
OngoingNotification.resetStartingtime();
JitsiMeetLogger.i(TAG + "Service stopped");
}
}

View File

@@ -43,6 +43,7 @@ class OngoingNotification {
private static final String CHANNEL_NAME = "Ongoing Conference Notifications";
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
private static long startingTime = 0;
static void createOngoingConferenceNotificationChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
@@ -85,6 +86,10 @@ class OngoingNotification {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID);
if (startingTime == 0) {
startingTime = System.currentTimeMillis();
}
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
@@ -92,6 +97,8 @@ class OngoingNotification {
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setWhen(startingTime)
.setUsesChronometer(true)
.setAutoCancel(false)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
@@ -110,6 +117,10 @@ class OngoingNotification {
return builder.build();
}
static void resetStartingtime() {
startingTime = 0;
}
private static NotificationCompat.Action createAction(Context context, JitsiMeetOngoingConferenceService.Action action, @StringRes int titleId) {
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
intent.setAction(action.getName());

View File

@@ -430,7 +430,7 @@ var config = {
// 'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
// 'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
// 'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
// 'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
// 'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
// ],
// Stats

View File

@@ -72,7 +72,7 @@
&--selected {
padding-left: 18px;
background: #131519;
background: $newToolbarBackgroundColor;
}
}
@@ -104,7 +104,7 @@
padding-left: 48px;
&--selected {
background: #131519;
background: $newToolbarBackgroundColor;
padding-left: 18px;
}
}

View File

@@ -23,44 +23,10 @@
flex-direction: row;
left: 50%;
position: absolute;
top: 10px;
bottom: 10px;
transform: translateX(-50%);
.toolbox-button {
&:first-child {
.toolbox-icon {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
}
&:nth-child(2) {
svg {
fill: $hangupColor;
}
}
&:last-child {
.toolbox-icon {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
}
}
}
.filmstrip-toolbox {
flex-direction: column;
.toolbox-button {
&:nth-child(1) {
svg {
fill: $hangupColor;
}
}
.toolbox-icon {
border-radius: 3px;
}
}
}
}

View File

@@ -39,6 +39,8 @@
}
&.settings-button-small-icon--disabled {
background: #36383C;
&> svg {
fill: #929292;
}

View File

@@ -75,6 +75,7 @@
&> div {
padding: 0;
background: $menuBG;
}
}
}
@@ -104,7 +105,7 @@
.toolbox-content-items {
background: #131519;
background: $newToolbarBackgroundColor;
box-shadow: 0px 2px 8px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.15);
border-radius: 6px;
margin: 0 auto;
@@ -235,6 +236,11 @@
}
}
@media (max-width: 320px) {
height: 36px;
width: 36px;
}
&.toggled {
background: $newToolbarButtonToggleColor;
}

View File

@@ -38,7 +38,7 @@ $presence-idle: rgb(172, 172, 172);
/**
* Toolbar
*/
$newToolbarBackgroundColor: rgba(22, 38, 55, 0.8);
$newToolbarBackgroundColor: #131519;
$newToolbarButtonHoverColor: rgba(255, 255, 255, 0.2);
$newToolbarButtonToggleColor: rgba(255, 255, 255, 0.15);
$AOTToolbarButtonHoverColor: rgba(14, 20, 35, 0.6);

View File

@@ -43,6 +43,7 @@ $flagsImagePath: "../images/";
@import 'modals/settings/settings';
@import 'modals/speaker_stats/speaker_stats';
@import 'modals/video-quality/video-quality';
@import 'modals/virtual-background/virtual-background';
@import 'modals/local-recording/local-recording';
@import 'videolayout_default';
@import 'notice';

View File

@@ -0,0 +1,44 @@
.virtual-background-dialog{
display: inline-flex;
cursor: pointer;
.thumbnail{
object-fit: cover;
padding: 5px;
height: 40px;
width: 40px;
}
.thumbnail-selected{
object-fit: cover;
padding: 5px;
height: 40px;
width: 40px;
border: 2px solid #a4b8d1;
}
.blur-selected{
border: 2px solid #a4b8d1;
}
.virtual-background-none{
font-weight: bold;
padding: 5px;
height: 35px;
width: 35px;
border-radius: 10px;
border: 1px solid #a4b8d1;
text-align: center;
vertical-align: middle;
line-height: 35px;
margin-right: 5px;
}
.none-selected{
font-weight: bold;
padding: 5px;
height: 35px;
width: 35px;
border-radius: 10px;
border: 2px solid #a4b8d1;
text-align: center;
vertical-align: middle;
line-height: 35px;
margin-right: 5px;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

View File

@@ -206,7 +206,7 @@ var interfaceConfig = {
// 'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
// 'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
// 'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
// 'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
// 'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
// ],
TOOLBAR_TIMEOUT: 4000,

View File

@@ -292,7 +292,7 @@ PODS:
- React
- react-native-splash-screen (3.2.0):
- React
- react-native-webrtc (1.87.3):
- react-native-webrtc (1.89.1):
- React-Core
- react-native-webview (11.0.2):
- React-Core
@@ -563,7 +563,7 @@ SPEC CHECKSUMS:
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
react-native-webrtc: dc1208bdca2c4d091f7b57859e69332bff6f1986
react-native-webrtc: ccb0c21eb4fb04326648fbdb4a5d49977e2cf274
react-native-webview: b2542d6fd424bcc3e3b2ec5f854f0abb4ec86c87
React-RCTActionSheet: bcbc311dc3b47bc8efb2737ff0940239a45789a9
React-RCTAnimation: 65f61080ce632f6dea23d52e354ffac9948396c6

View File

@@ -51,7 +51,7 @@
if ([FIRUtilities appContainsRealServiceInfoPlist]) {
NSLog(@"Enabling Firebase");
[FIRApp configure];
// Crashlytics defaults to disabled wirth the FirebaseCrashlyticsCollectionEnabled Info.plist key.
// Crashlytics defaults to disabled with the FirebaseCrashlyticsCollectionEnabled Info.plist key.
[[FIRCrashlytics crashlytics] setCrashlyticsCollectionEnabled:![jitsiMeet isCrashReportingDisabled]];
}

View File

@@ -35,7 +35,7 @@ fi
echo "PR_REPO_SLUG=${PR_REPO_SLUG} PR_BRANCH=${PR_BRANCH}"
# do the marge and git log
# do the merge and git log
if [ $PR_BRANCH != "master" ]; then
echo "Will merge ${PR_REPO_SLUG}/${PR_BRANCH} into master"

View File

@@ -90,7 +90,7 @@ echo "importing dev-key.p12"
security import ${CERT_DIR}/dev-key.p12 -k ios-build.keychain -P $IOS_SIGNING_CERT_PASSWORD -A
echo "will set-key-partition-list"
# Fix for OS X Sierra that hungs in the codesign step
# Fix for OS X Sierra that hangs in the codesign step
security set-key-partition-list -S apple-tool:,apple: -s -k $ENCRYPTION_PASSWORD ios-build.keychain > /dev/null
echo "done set-key-partition-list"

View File

@@ -184,7 +184,7 @@ static void initializeViewsMap() {
// conference. However, React and, respectively,
// appProperties/initialProperties are declarative expressions i.e. one and
// the same URL will not trigger an automatic re-render in the JavaScript
// source code. The workaround implemented bellow introduces imperativeness
// source code. The workaround implemented below introduces imperativeness
// in React Component props by defining a unique value per invocation.
props[@"timestamp"] = @(mach_absolute_time());

View File

@@ -76,7 +76,7 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
// Avoid mute actions ping-pong: if the mute action was caused by
// the JS side (we requested a transaction) don't call the delegate
// method. If it was called by the provder itself (when the user presses
// method. If it was called by the provider itself (when the user presses
// the mute button in the CallKit view) then call the delegate method.
//
// NOTE: don't try to be clever and remove this. Been there, done that.

View File

@@ -29,7 +29,7 @@ public protocol PiPViewCoordinatorDelegate: class {
/// when is presented in Picure in Picture mode.
public class PiPViewCoordinator {
/// Limits the boundries of view position on screen when minimized
/// Limits the boundaries of view position on screen when minimized
public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25,
left: 5,
bottom: 5,

View File

@@ -60,7 +60,7 @@ fi
echo "PR_REPO_SLUG=${PR_REPO_SLUG} PR_BRANCH=${PR_BRANCH}"
# do the marge and git log
# do the merge and git log
if [ $PR_BRANCH != "master" ]; then
echo "Will merge ${PR_REPO_SLUG}/${PR_BRANCH} into master"

View File

@@ -332,6 +332,11 @@
"embedMeeting": {
"title": "Embed this meeting"
},
"virtualBackground": {
"title": "Backgrounds",
"enableBlur": "Enable blur",
"removeBackground": "Remove background"
},
"feedback": {
"average": "Average",
"bad": "Bad",
@@ -748,7 +753,7 @@
"toggleCamera": "Toggle camera",
"toggleFilmstrip": "Toggle filmstrip",
"videomute": "Toggle mute video",
"videoblur": "Toggle video blur"
"selectBackground": "Select Background"
},
"addPeople": "Add people to your call",
"audioSettings": "Audio settings",
@@ -810,9 +815,7 @@
"tileViewToggle": "Toggle tile view",
"toggleCamera": "Toggle camera",
"videomute": "Start / Stop camera",
"videoSettings": "Video settings",
"startvideoblur": "Blur my background",
"stopvideoblur": "Disable background blur"
"selectBackground": "Select background"
},
"transcribing": {
"ccButtonTooltip": "Start / Stop subtitles",

View File

@@ -197,7 +197,7 @@ function parseArguments(args) {
* @param {any} value - The value to be parsed.
* @returns {string|undefined} The parsed value that can be used for setting
* sizes through the style property. If invalid value is passed the method
* retuns undefined.
* returns undefined.
*/
function parseSizeParam(value) {
let parsedValue;

View File

@@ -63,7 +63,7 @@ function doExternalAuth(room, lockPassword) {
/**
* Redirect the user to the token authentication service for the login to be
* performed. Once complete it is expected that the service wil bring the user
* performed. Once complete it is expected that the service will bring the user
* back with "?jwt={the JWT token}" query parameter added.
* @param {string} [roomName] the name of the conference room.
*/
@@ -76,7 +76,7 @@ function redirectToTokenAuthService(roomName) {
/**
* Initializes 'message' listener that will wait for a JWT token to be received
* from the token authentication service opened in a popup window.
* @param room the name fo the conference room.
* @param room the name of the conference room.
*/
function initJWTTokenListener(room) {
/**
@@ -108,7 +108,7 @@ function initJWTTokenListener(room) {
roomName, APP.conference._getConferenceOptions());
// Authenticate from the new connection to get
// the session-ID from the focus, which wil then be used
// the session-ID from the focus, which will then be used
// to upgrade current connection's user role
newRoom.room.moderator.authenticate()
@@ -116,7 +116,7 @@ function initJWTTokenListener(room) {
connection.disconnect();
// At this point we'll have session-ID stored in
// the settings. It wil be used in the call below
// the settings. It will be used in the call below
// to upgrade user's role
room.room.moderator.authenticate()
.then(() => {

View File

@@ -498,7 +498,7 @@ export default class SharedVideoManager {
* Receives events for local audio mute/unmute by local user.
* @param muted boolena whether it is muted or not.
* @param {boolean} indicates if this mute was a result of user interaction,
* i.e. pressing the mute button or it was programatically triggerred
* i.e. pressing the mute button or it was programmatically triggered
*/
onLocalAudioMuted(muted, userInteraction) {
if (!this.player) {

View File

@@ -37,7 +37,7 @@ const UIUtil = {
* @param {string} url - The redirect URL.
* NOTE: Currently used to redirect to 3rd party location for
* authentication. In most cases redirectWithStoredParams action must be
* used instead of this method in order to preserve curent URL params.
* used instead of this method in order to preserve current URL params.
*/
redirect(url) {
window.location.href = url;

View File

@@ -350,7 +350,7 @@ export default class SmallVideo {
}
/**
* Initalizes any browser specific properties. Currently sets the overflow
* Initializes any browser specific properties. Currently sets the overflow
* property for Qt browsers on Windows to hidden, thus fixing the following
* problem:
* Some browsers don't have full support of the object-fit property for the
@@ -428,9 +428,9 @@ export default class SmallVideo {
_shouldTriggerPin(event) {
// TODO Checking the classes is a workround to allow events to bubble into
// the DisplayName component if it was clicked. React's synthetic events
// will fire after jQuery handlers execute, so stop propogation at this
// will fire after jQuery handlers execute, so stop propagation at this
// point will prevent DisplayName from getting click events. This workaround
// should be removeable once LocalVideo is a React Component because then
// should be removable once LocalVideo is a React Component because then
// the components share the same eventing system.
const $source = $(event.target || event.srcElement);

View File

@@ -46,7 +46,7 @@ function computeDesktopVideoSize( // eslint-disable-line max-params
videoSpaceWidth,
videoSpaceHeight) {
if (videoWidth === 0 || videoHeight === 0 || videoSpaceWidth === 0 || videoSpaceHeight === 0) {
// Avoid NaN values caused by devision by 0.
// Avoid NaN values caused by division by 0.
return [ 0, 0 ];
}
@@ -94,7 +94,7 @@ function computeCameraVideoSize( // eslint-disable-line max-params
videoSpaceHeight,
videoLayoutFit) {
if (videoWidth === 0 || videoHeight === 0 || videoSpaceWidth === 0 || videoSpaceHeight === 0) {
// Avoid NaN values caused by devision by 0.
// Avoid NaN values caused by division by 0.
return [ 0, 0 ];
}
@@ -411,7 +411,7 @@ export class VideoContainer extends LargeContainer {
const [ width, height ] = this._getVideoSize(containerWidth, containerHeight);
if (width === 0 || height === 0) {
// We don't need to set 0 for width or height since the visibility is controled by the visibility css prop
// We don't need to set 0 for width or height since the visibility is controlled by the visibility css prop
// on the largeVideoElementsContainer. Also if the width/height of the video element is 0 the attached
// stream won't be played. Normally if we attach a new stream we won't resize the video element until the
// stream has been played. But setting width/height to 0 will prevent the video from playing.

View File

@@ -596,7 +596,7 @@ const VideoLayout = {
localVideoThumbnail && localVideoThumbnail.updateDOMLocation();
VideoLayout.resizeVideoArea();
// Rerender the thumbnails since they are dependant on the layout because of the tooltip positioning.
// Rerender the thumbnails since they are dependent on the layout because of the tooltip positioning.
localVideoThumbnail && localVideoThumbnail.rerender();
Object.values(remoteVideos).forEach(remoteVideoThumbnail => remoteVideoThumbnail.rerender());
},

15
package-lock.json generated
View File

@@ -2586,9 +2586,9 @@
}
},
"@jitsi/js-utils": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.5.tgz",
"integrity": "sha512-1APQyuqQaYDR+W7cdgzsaBo6x8dpF8sfelcBf3ngNU3Jd+DzuuwUvCMTbr2+cCuy6w59ZAuQ7e2ixCnnOXOW4Q==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.6.tgz",
"integrity": "sha512-XdLvgzhEhMsrzHBDgBydvWneGXSZ8ycg+dEK9bs2CLQhH5A9cHOOToDl9p/skts7WHQp4C8nkhsNzWC/cRvszQ==",
"requires": {
"bowser": "2.7.0",
"js-md5": "0.7.3"
@@ -10265,8 +10265,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#0ec072378c84206b90f0e001e34cf4d746384cc5",
"from": "github:jitsi/lib-jitsi-meet#0ec072378c84206b90f0e001e34cf4d746384cc5",
"version": "github:jitsi/lib-jitsi-meet#676c7a910505833810314a665ad1e825a158850c",
"from": "github:jitsi/lib-jitsi-meet#676c7a910505833810314a665ad1e825a158850c",
"requires": {
"@jitsi/js-utils": "1.0.2",
"@jitsi/sdp-interop": "1.0.3",
@@ -14077,8 +14077,9 @@
"integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
},
"react-native-webrtc": {
"version": "github:react-native-webrtc/react-native-webrtc#1066b92d48048d67ff23288d68ab1734ec364cab",
"from": "github:react-native-webrtc/react-native-webrtc#1066b92d48048d67ff23288d68ab1734ec364cab",
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.89.1.tgz",
"integrity": "sha512-YBvSoSpw3dvRQr5LcOmdS/56arCBwtrIqdgPuYURbGheJbGAE/wYfeqMKa2fiIQ6EoYkFNZPDV5n4M7/f3KvlQ==",
"requires": {
"base64-js": "^1.1.2",
"cross-os": "^1.3.0",

View File

@@ -32,7 +32,7 @@
"@atlaskit/theme": "11.0.2",
"@atlaskit/toggle": "12.0.3",
"@atlaskit/tooltip": "17.1.2",
"@jitsi/js-utils": "1.0.5",
"@jitsi/js-utils": "1.0.6",
"@microsoft/microsoft-graph-client": "1.1.0",
"@react-native-async-storage/async-storage": "1.13.2",
"@react-native-community/google-signin": "3.0.1",
@@ -55,7 +55,7 @@
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#0ec072378c84206b90f0e001e34cf4d746384cc5",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#676c7a910505833810314a665ad1e825a158850c",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.21",
"moment": "2.29.1",
@@ -84,7 +84,7 @@
"react-native-svg-transformer": "0.14.3",
"react-native-url-polyfill": "1.2.0",
"react-native-watch-connectivity": "0.4.3",
"react-native-webrtc": "github:react-native-webrtc/react-native-webrtc#1066b92d48048d67ff23288d68ab1734ec364cab",
"react-native-webrtc": "1.89.1",
"react-native-webview": "11.0.2",
"react-native-youtube-iframe": "1.2.3",
"react-redux": "7.1.0",

View File

@@ -48,7 +48,7 @@ export default class Toolbar extends Component<Props> {
return (
<div
className = { `always-on-top-toolbox ${className}` }
className = { `toolbox-content-items always-on-top-toolbox ${className}` }
onMouseOut = { onMouseOut }
onMouseOver = { onMouseOver }>
<AudioMuteButton />

View File

@@ -25,7 +25,6 @@ import '../base/testing/reducer';
import '../base/tracks/reducer';
import '../base/user-interaction/reducer';
import '../billing-counter/reducer';
import '../blur/reducer';
import '../calendar-sync/reducer';
import '../chat/reducer';
import '../deep-linking/reducer';

View File

@@ -12,5 +12,5 @@ import '../remote-control/reducer';
import '../screenshot-capture/reducer';
import '../shared-video/reducer';
import '../talk-while-muted/reducer';
import '../virtual-background/reducer';
import './reducers.any';

View File

@@ -18,5 +18,5 @@ export const TOOLBAR_BUTTONS = [
'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
];

View File

@@ -43,7 +43,11 @@ export const bottomSheetStyles = {
alignItems: 'stretch',
flex: 1,
flexDirection: 'column',
justifyContent: 'flex-end'
justifyContent: 'flex-end',
maxWidth: 500,
marginLeft: 'auto',
marginRight: 'auto',
width: '100%'
},
sheetItemContainer: {

View File

@@ -109,6 +109,7 @@ export { default as IconVideoQualityAudioOnly } from './AUD.svg';
export { default as IconVideoQualityHD } from './HD.svg';
export { default as IconVideoQualityLD } from './LD.svg';
export { default as IconVideoQualitySD } from './SD.svg';
export { default as IconVirtualBackground } from './virtual-background.svg';
export { default as IconVolume } from './volume.svg';
export { default as IconVolumeEmpty } from './volume-empty.svg';
export { default as IconVolumeOff } from './volume-off.svg';

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="white" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.6666 1.66667H3.33329C2.41282 1.66667 1.66663 2.41286 1.66663 3.33334V16.6667C1.66663 17.5871 2.41282 18.3333 3.33329 18.3333H3.63257H8.56767H9.14753H15.6942H16.6666C17.5871 18.3333 18.3333 17.5871 18.3333 16.6667V3.33334C18.3333 2.41286 17.5871 1.66667 16.6666 1.66667ZM7.57977 12.3005L9.3687 14.893L12.0223 8.76728C12.2052 8.34496 12.6959 8.15091 13.1182 8.33385C13.2964 8.41106 13.4421 8.54811 13.53 8.72131L16.6666 14.9002V3.33334H3.33329V16.4665L6.208 12.3005C6.4694 11.9217 6.98838 11.8265 7.36718 12.0879C7.45035 12.1453 7.52238 12.2174 7.57977 12.3005ZM7.49996 10C6.11925 10 4.99996 8.88072 4.99996 7.5C4.99996 6.11929 6.11925 5.00001 7.49996 5.00001C8.88067 5.00001 9.99996 6.11929 9.99996 7.5C9.99996 8.88072 8.88067 10 7.49996 10ZM8.33329 7.5C8.33329 7.96024 7.9602 8.33334 7.49996 8.33334C7.03972 8.33334 6.66663 7.96024 6.66663 7.5C6.66663 7.03977 7.03972 6.66667 7.49996 6.66667C7.9602 6.66667 8.33329 7.03977 8.33329 7.5ZM12.8466 11.0572L15.6942 16.6667H10.4167L12.8466 11.0572ZM6.89389 14.2411L8.56767 16.6667H5.2201L6.89389 14.2411Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,7 +1,7 @@
// @flow
import { getBlurEffect } from '../../blur';
import { createScreenshotCaptureEffect } from '../../stream-effects/screenshot-capture';
import { createVirtualBackgroundEffect } from '../../stream-effects/virtual-background';
import logger from './logger';
@@ -13,11 +13,12 @@ import logger from './logger';
*/
export default function loadEffects(store: Object): Promise<any> {
const state = store.getState();
const virtualBackground = state['features/virtual-background'];
const blurPromise = state['features/blur'].blurEnabled
? getBlurEffect()
const backgroundPromise = virtualBackground.backgroundEffectEnabled
? createVirtualBackgroundEffect(virtualBackground)
.catch(error => {
logger.error('Failed to obtain the blur effect instance with error: ', error);
logger.error('Failed to obtain the background effect instance with error: ', error);
return Promise.resolve();
})
@@ -31,5 +32,5 @@ export default function loadEffects(store: Object): Promise<any> {
})
: Promise.resolve();
return Promise.all([ blurPromise, screenshotCapturePromise ]);
return Promise.all([ backgroundPromise, screenshotCapturePromise ]);
}

View File

@@ -1,7 +1,7 @@
/**
* The key for the billing id stored in localStorage.
*/
export const BILLING_ID = 'billingId';
export const BILLING_ID = 'jitsiMeetId';
/**
* The prefix for the vpaas tenant.

View File

@@ -1,21 +0,0 @@
// @flow
/**
* The type of redux action dispatched which represents that the blur
* is enabled.
*
* {
* type: BLUR_ENABLED
* }
*/
export const BLUR_ENABLED = 'BLUR_ENABLED';
/**
* The type of redux action dispatched which represents that the blur
* is disabled.
*
* {
* type: BLUR_DISABLED
* }
*/
export const BLUR_DISABLED = 'BLUR_DISABLED';

View File

@@ -1,67 +0,0 @@
// @flow
import { getLocalVideoTrack } from '../../features/base/tracks';
import { BLUR_DISABLED, BLUR_ENABLED } from './actionTypes';
import { getBlurEffect } from './functions';
import logger from './logger';
/**
* Signals the local participant is switching between blurred or non blurred video.
*
* @param {boolean} enabled - If true enables video blur, false otherwise.
* @returns {Promise}
*/
export function toggleBlurEffect(enabled: boolean) {
return function(dispatch: (Object) => Object, getState: () => any) {
const state = getState();
if (state['features/blur'].blurEnabled !== enabled) {
const { jitsiTrack } = getLocalVideoTrack(state['features/base/tracks']);
return getBlurEffect()
.then(blurEffectInstance =>
jitsiTrack.setEffect(enabled ? blurEffectInstance : undefined)
.then(() => {
enabled ? dispatch(blurEnabled()) : dispatch(blurDisabled());
})
.catch(error => {
enabled ? dispatch(blurDisabled()) : dispatch(blurEnabled());
logger.error('setEffect failed with error:', error);
})
)
.catch(error => {
dispatch(blurDisabled());
logger.error('getBlurEffect failed with error:', error);
});
}
return Promise.resolve();
};
}
/**
* Signals the local participant that the blur has been enabled.
*
* @returns {{
* type: BLUR_ENABLED
* }}
*/
export function blurEnabled() {
return {
type: BLUR_ENABLED
};
}
/**
* Signals the local participant that the blur has been disabled.
*
* @returns {{
* type: BLUR_DISABLED
* }}
*/
export function blurDisabled() {
return {
type: BLUR_DISABLED
};
}

View File

@@ -1 +0,0 @@
export { default as VideoBlurButton } from './VideoBlurButton';

View File

@@ -1,38 +0,0 @@
// @flow
import { getJitsiMeetGlobalNS, loadScript } from '../base/util';
let filterSupport;
/**
* Returns promise that resolves with the blur effect instance.
*
* @returns {Promise<JitsiStreamBlurEffect>} - Resolves with the blur effect instance.
*/
export function getBlurEffect() {
const ns = getJitsiMeetGlobalNS();
if (ns.effects && ns.effects.createBlurEffect) {
return ns.effects.createBlurEffect();
}
return loadScript('libs/video-blur-effect.min.js').then(() => ns.effects.createBlurEffect());
}
/**
* Checks context filter support.
*
* @returns {boolean} True if the filter is supported and false if the filter is not supported by the browser.
*/
export function checkBlurSupport() {
if (typeof filterSupport === 'undefined') {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
filterSupport = typeof ctx.filter !== 'undefined';
canvas.remove();
}
return filterSupport;
}

View File

@@ -1,26 +0,0 @@
// @flow
import { ReducerRegistry } from '../base/redux';
import { BLUR_ENABLED, BLUR_DISABLED } from './actionTypes';
ReducerRegistry.register('features/blur', (state = {}, action) => {
switch (action.type) {
case BLUR_ENABLED: {
return {
...state,
blurEnabled: true
};
}
case BLUR_DISABLED: {
return {
...state,
blurEnabled: false
};
}
}
return state;
});

View File

@@ -6,7 +6,7 @@ import { toState } from '../base/redux';
import { getBackendSafePath, getJitsiMeetGlobalNS } from '../base/util';
import { getVpaasBillingId } from '../billing-counter/functions';
import { showWarningNotification } from '../notifications';
import { createRnnoiseProcessorPromise } from '../rnnoise';
import { createRnnoiseProcessor } from '../stream-effects/rnnoise';
export * from './functions.any';
@@ -84,7 +84,7 @@ export function getConferenceOptions(stateful) {
options.applicationName = getName();
options.getWiFiStatsMethod = getWiFiStatsMethod;
options.createVADProcessor = createRnnoiseProcessorPromise;
options.createVADProcessor = createRnnoiseProcessor;
options.billingId = getVpaasBillingId(state);
// Disable CallStats, if requessted.

View File

@@ -1,27 +0,0 @@
// @flow
import { getJitsiMeetGlobalNS, loadScript } from '../base/util';
let loadRnnoisePromise;
/**
* Returns promise that resolves with a RnnoiseProcessor instance.
*
* @returns {Promise<RnnoiseProcessor>} - Resolves with the blur effect instance.
*/
export function createRnnoiseProcessorPromise() {
// Subsequent calls should not attempt to load the script multiple times.
if (!loadRnnoisePromise) {
loadRnnoisePromise = loadScript('libs/rnnoise-processor.min.js');
}
return loadRnnoisePromise.then(() => {
const ns = getJitsiMeetGlobalNS();
if (ns?.effects?.rnnoise?.createRnnoiseProcessor) {
return ns.effects.rnnoise.createRnnoiseProcessor();
}
throw new Error('Rnnoise module binding createRnnoiseProcessor not found!');
});
}

View File

@@ -1,2 +0,0 @@
export * from './functions';

View File

@@ -14,6 +14,16 @@ export class AudioMixerEffect {
*/
_mixAudio: Object;
/**
* Original MediaStream from the JitsiLocalTrack that uses this effect.
*/
_originalStream: Object;
/**
* MediaStreamTrack obtained from the original MediaStream.
*/
_originalTrack: Object;
/**
* lib-jitsi-meet AudioMixer.
*/
@@ -51,9 +61,12 @@ export class AudioMixerEffect {
* @returns {MediaStream} - MediaStream containing both audio tracks mixed together.
*/
startEffect(audioStream: MediaStream) {
this._originalStream = audioStream;
this._originalTrack = audioStream.getTracks()[0];
this._audioMixer = JitsiMeetJS.createAudioMixer();
this._audioMixer.addMediaStream(this._mixAudio.getOriginalStream());
this._audioMixer.addMediaStream(audioStream);
this._audioMixer.addMediaStream(this._originalStream);
return this._audioMixer.start();
}
@@ -67,4 +80,22 @@ export class AudioMixerEffect {
this._audioMixer.reset();
}
/**
* Change the muted state of the effect.
*
* @param {boolean} muted - Should effect be muted or not.
* @returns {void}
*/
setMuted(muted: boolean) {
this._originalTrack.enabled = !muted;
}
/**
* Check wether or not this effect is muted.
*
* @returns {boolean}
*/
isMuted() {
return !this._originalTrack.enabled;
}
}

View File

@@ -1,61 +0,0 @@
// @flow
import * as wasmCheck from 'wasm-check';
import JitsiStreamBlurEffect from './JitsiStreamBlurEffect';
import createTFLiteModule from './vendor/tflite/tflite';
import createTFLiteSIMDModule from './vendor/tflite/tflite-simd';
const models = {
'model96': 'libs/segm_lite_v681.tflite',
'model144': 'libs/segm_full_v679.tflite'
};
const segmentationDimensions = {
'model96': {
'height': 96,
'width': 160
},
'model144': {
'height': 144,
'width': 256
}
};
/**
* Creates a new instance of JitsiStreamBlurEffect. This loads the bodyPix model that is used to
* extract person segmentation.
*
* @returns {Promise<JitsiStreamBlurEffect>}
*/
export async function createBlurEffect() {
if (!MediaStreamTrack.prototype.getSettings && !MediaStreamTrack.prototype.getConstraints) {
throw new Error('JitsiStreamBlurEffect not supported!');
}
let tflite;
if (wasmCheck.feature.simd) {
tflite = await createTFLiteSIMDModule();
} else {
tflite = await createTFLiteModule();
}
const modelBufferOffset = tflite._getModelBufferMemoryOffset();
const modelResponse = await fetch(
wasmCheck.feature.simd ? models.model144 : models.model96
);
if (!modelResponse.ok) {
throw new Error('Failed to download tflite model!');
}
const model = await modelResponse.arrayBuffer();
tflite.HEAPU8.set(new Uint8Array(model), modelBufferOffset);
tflite._loadModel(model.byteLength);
const options = wasmCheck.feature.simd ? segmentationDimensions.model144 : segmentationDimensions.model96;
return new JitsiStreamBlurEffect(tflite, options);
}

View File

@@ -9,11 +9,11 @@ import {
const blurValue = '25px';
/**
* Represents a modified MediaStream that adds blur to video background.
* <tt>JitsiStreamBlurEffect</tt> does the processing of the original
* Represents a modified MediaStream that adds effects to video background.
* <tt>JitsiStreamBackgroundEffect</tt> does the processing of the original
* video stream.
*/
export default class JitsiStreamBlurEffect {
export default class JitsiStreamBackgroundEffect {
_model: Object;
_options: Object;
_segmentationPixelCount: number;
@@ -26,6 +26,7 @@ export default class JitsiStreamBlurEffect {
_segmentationMask: Object;
_segmentationMaskCanvas: Object;
_renderMask: Function;
_virtualImage: HTMLImageElement;
isEnabled: Function;
startEffect: Function;
stopEffect: Function;
@@ -38,6 +39,12 @@ export default class JitsiStreamBlurEffect {
* @param {Object} options - Segmentation dimensions.
*/
constructor(model: Object, options: Object) {
this._options = options;
if (this._options.virtualBackground.isVirtualBackground) {
this._virtualImage = document.createElement('img');
this._virtualImage.src = this._options.virtualBackground.virtualSource;
}
this._model = model;
this._options = options;
this._segmentationPixelCount = this._options.width * this._options.height;
@@ -73,7 +80,15 @@ export default class JitsiStreamBlurEffect {
this._outputCanvasCtx.globalCompositeOperation = 'copy';
// Draw segmentation mask.
this._outputCanvasCtx.filter = `blur(${blurValue})`;
//
// Smooth out the edges.
if (this._options.virtualBackground.isVirtualBackground) {
this._outputCanvasCtx.filter = 'blur(4px)';
} else {
this._outputCanvasCtx.filter = 'blur(8px)';
}
this._outputCanvasCtx.drawImage(
this._segmentationMaskCanvas,
0,
@@ -85,14 +100,24 @@ export default class JitsiStreamBlurEffect {
this._inputVideoElement.width,
this._inputVideoElement.height
);
this._outputCanvasCtx.globalCompositeOperation = 'source-in';
this._outputCanvasCtx.filter = 'none';
// Draw the foreground video.
//
this._outputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
// Draw the background.
//
this._outputCanvasCtx.globalCompositeOperation = 'destination-over';
this._outputCanvasCtx.filter = `blur(${blurValue})`;
this._outputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
if (this._options.virtualBackground.isVirtualBackground) {
this._outputCanvasCtx.drawImage(this._virtualImage, 0, 0);
} else {
this._outputCanvasCtx.filter = `blur(${blurValue})`;
this._outputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
}
}
/**
@@ -196,6 +221,7 @@ export default class JitsiStreamBlurEffect {
this._segmentationMaskCanvas.width = this._options.width;
this._segmentationMaskCanvas.height = this._options.height;
this._segmentationMaskCtx = this._segmentationMaskCanvas.getContext('2d');
this._outputCanvasElement.width = parseInt(width, 10);
this._outputCanvasElement.height = parseInt(height, 10);
this._outputCanvasCtx = this._outputCanvasElement.getContext('2d');

View File

@@ -0,0 +1,64 @@
// @flow
import * as wasmCheck from 'wasm-check';
import JitsiStreamBackgroundEffect from './JitsiStreamBackgroundEffect';
import createTFLiteModule from './vendor/tflite/tflite';
import createTFLiteSIMDModule from './vendor/tflite/tflite-simd';
const models = {
model96: 'libs/segm_lite_v681.tflite',
model144: 'libs/segm_full_v679.tflite'
};
const segmentationDimensions = {
model96: {
height: 96,
width: 160
},
model144: {
height: 144,
width: 256
}
};
/**
* Creates a new instance of JitsiStreamBackgroundEffect. This loads the Meet background model that is used to
* extract person segmentation.
*
* @param {Object} virtualBackground - The virtual object that contains the background image source and
* the isVirtualBackground flag that indicates if virtual image is activated.
* @returns {Promise<JitsiStreamBackgroundEffect>}
*/
export async function createVirtualBackgroundEffect(virtualBackground: Object) {
if (!MediaStreamTrack.prototype.getSettings && !MediaStreamTrack.prototype.getConstraints) {
throw new Error('JitsiStreamBackgroundEffect not supported!');
}
let tflite;
if (wasmCheck.feature.simd) {
tflite = await createTFLiteSIMDModule();
} else {
tflite = await createTFLiteModule();
}
const modelBufferOffset = tflite._getModelBufferMemoryOffset();
const modelResponse = await fetch(wasmCheck.feature.simd ? models.model144 : models.model96);
if (!modelResponse.ok) {
throw new Error('Failed to download tflite model!');
}
const model = await modelResponse.arrayBuffer();
tflite.HEAPU8.set(new Uint8Array(model), modelBufferOffset);
tflite._loadModel(model.byteLength);
const options = {
...wasmCheck.feature.simd ? segmentationDimensions.model144 : segmentationDimensions.model96,
virtualBackground
};
return new JitsiStreamBackgroundEffect(tflite, options);
}

View File

@@ -65,6 +65,7 @@ const styles = {
toolbar: {
alignItems: 'center',
backgroundColor: ColorPalette.darkBackground,
borderRadius: 3,
flexDirection: 'row',
flexGrow: 0,
justifyContent: 'space-between',
@@ -77,7 +78,11 @@ const styles = {
*/
toolbox: {
flexDirection: 'column',
flexGrow: 0
flexGrow: 0,
width: '100%',
maxWidth: 500,
marginLeft: 'auto',
marginRight: 'auto'
}
};

View File

@@ -35,8 +35,6 @@ import { connect } from '../../../base/redux';
import { OverflowMenuItem } from '../../../base/toolbox/components';
import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
import { isVpaasMeeting } from '../../../billing-counter/functions';
import { VideoBlurButton } from '../../../blur';
import { checkBlurSupport } from '../../../blur/functions';
import { CHAT_SIZE, ChatCounter, toggleChat } from '../../../chat';
import { EmbedMeetingDialog } from '../../../embed-meeting';
import { SharedDocumentButton } from '../../../etherpad';
@@ -68,6 +66,8 @@ import {
OverflowMenuVideoQualityItem,
VideoQualityDialog
} from '../../../video-quality';
import { VideoBackgroundButton } from '../../../virtual-background';
import { checkBlurSupport } from '../../../virtual-background/functions';
import {
setFullScreen,
setOverflowMenuVisible,
@@ -1017,9 +1017,9 @@ class Toolbox extends Component<Props, State> {
&& <SharedDocumentButton
key = 'etherpad'
showLabel = { true } />,
this._shouldShowButton('videobackgroundblur')
&& <VideoBlurButton
key = 'videobackgroundblur'
(this._shouldShowButton('select-background') || this._shouldShowButton('videobackgroundblur'))
&& <VideoBackgroundButton
key = { 'select-background' }
showLabel = { true }
visible = { !_screensharing && checkBlurSupport() } />,
this._shouldShowButton('stats')
@@ -1134,7 +1134,9 @@ class Toolbox extends Component<Props, State> {
if (this._shouldShowButton('chat')) {
buttons.has('chat')
? mainMenuAdditionalButtons.push(<div className = 'toolbar-button-with-badge'>
? mainMenuAdditionalButtons.push(<div
className = 'toolbar-button-with-badge'
key = 'chatcontainer'>
<ToolbarButton
accessibilityLabel = { t('toolbar.accessibilityLabel.chat') }
icon = { IconChat }
@@ -1173,8 +1175,7 @@ class Toolbox extends Component<Props, State> {
? mainMenuAdditionalButtons.push(
<TileViewButton
key = 'tileview'
showLabel = { false }
visible = { true } />)
showLabel = { false } />)
: overflowMenuAdditionalButtons.push(
<TileViewButton
key = 'tileview'

View File

@@ -0,0 +1,23 @@
// @flow
/**
* The type of redux action dispatched which represents that the background
* effect is enabled or not.
*
* @returns {{
* type: BACKGROUND_ENABLED,
* backgroundEffectEnabled: boolean,
* }}
*/
export const BACKGROUND_ENABLED = 'BACKGROUND_ENABLED';
/**
* The type of the action which enables or disables virtual background
*
* @returns {{
* type: SET_VIRTUAL_BACKGROUND,
* isVirtualBackground: boolean,
* virtualSource: string,
* }}
*/
export const SET_VIRTUAL_BACKGROUND = 'SET_VIRTUAL_BACKGROUND';

View File

@@ -0,0 +1,70 @@
// @flow
import { getLocalVideoTrack } from '../base/tracks';
import { createVirtualBackgroundEffect } from '../stream-effects/virtual-background';
import { BACKGROUND_ENABLED, SET_VIRTUAL_BACKGROUND } from './actionTypes';
import logger from './logger';
/**
* Signals the local participant activate the virtual background video or not.
*
* @param {boolean} enabled - If true enables video background, false otherwise.
* @returns {Promise}
*/
export function toggleBackgroundEffect(enabled: boolean) {
return async function(dispatch: Object => Object, getState: () => any) {
const state = getState();
const { jitsiTrack } = getLocalVideoTrack(state['features/base/tracks']);
const virtualBackground = state['features/virtual-background'];
try {
if (enabled) {
await jitsiTrack.setEffect(await createVirtualBackgroundEffect(virtualBackground));
dispatch(backgroundEnabled(true));
} else {
await jitsiTrack.setEffect(undefined);
dispatch(backgroundEnabled(false));
}
} catch (error) {
dispatch(backgroundEnabled(false));
logger.error('Error on apply backgroun effect:', error);
}
};
}
/**
* Sets the selected virtual background image object.
*
* @param {Object} virtualSource - Virtual background image source.
* @param {boolean} isVirtualBackground - Indicate if virtual image is activated.
* @returns {{
* type: SET_VIRTUAL_BACKGROUND,
* virtualSource: string,
* isVirtualBackground: boolean,
* }}
*/
export function setVirtualBackground(virtualSource: string, isVirtualBackground: boolean) {
return {
type: SET_VIRTUAL_BACKGROUND,
virtualSource,
isVirtualBackground
};
}
/**
* Signals the local participant that the background effect has been enabled.
*
* @param {boolean} backgroundEffectEnabled - Indicate if virtual background effect is activated.
* @returns {{
* type: BACKGROUND_ENABLED,
* backgroundEffectEnabled: boolean,
* }}
*/
export function backgroundEnabled(backgroundEffectEnabled: boolean) {
return {
type: BACKGROUND_ENABLED,
backgroundEffectEnabled
};
}

View File

@@ -1,23 +1,24 @@
// @flow
import { createVideoBlurEvent, sendAnalytics } from '../../analytics';
import { openDialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
import { IconBlurBackground } from '../../base/icons';
import { IconVirtualBackground } from '../../base/icons';
import { connect } from '../../base/redux';
import { AbstractButton } from '../../base/toolbox/components';
import type { AbstractButtonProps } from '../../base/toolbox/components';
import { isLocalCameraTrackMuted } from '../../base/tracks';
import { toggleBlurEffect } from '../actions';
import { VirtualBackgroundDialog } from './index';
/**
* The type of the React {@code Component} props of {@link VideoBlurButton}.
* The type of the React {@code Component} props of {@link VideoBackgroundButton}.
*/
type Props = AbstractButtonProps & {
/**
* True if the video background is blurred or false if it is not.
*/
_isVideoBlurred: boolean,
_isBackgroundEnabled: boolean,
/**
* Whether video is currently muted or not.
@@ -28,42 +29,39 @@ type Props = AbstractButtonProps & {
* The redux {@code dispatch} function.
*/
dispatch: Function
};
/**
* An abstract implementation of a button that toggles the video blur effect.
* An abstract implementation of a button that toggles the video background dialog.
*/
class VideoBlurButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.videoblur';
icon = IconBlurBackground;
label = 'toolbar.startvideoblur';
toggledLabel = 'toolbar.stopvideoblur';
class VideoBackgroundButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.selectBackground';
icon = IconVirtualBackground;
label = 'toolbar.selectBackground';
tooltip = 'toolbar.selectBackground';
/**
* Handles clicking / pressing the button, and toggles the blur effect
* Handles clicking / pressing the button, and toggles the virtual background dialog
* state accordingly.
*
* @protected
* @returns {void}
*/
_handleClick() {
const { _isVideoBlurred, dispatch } = this.props;
const value = !_isVideoBlurred;
const { dispatch } = this.props;
sendAnalytics(createVideoBlurEvent(value ? 'started' : 'stopped'));
dispatch(toggleBlurEffect(value));
dispatch(openDialog(VirtualBackgroundDialog));
}
/**
* Returns {@code boolean} value indicating if the blur effect is
* Returns {@code boolean} value indicating if the background effect is
* enabled or not.
*
* @protected
* @returns {boolean}
*/
_isToggled() {
return this.props._isVideoBlurred;
return this.props._isBackgroundEnabled;
}
/**
@@ -80,22 +78,21 @@ class VideoBlurButton extends AbstractButton<Props, *> {
/**
* Maps (parts of) the redux state to the associated props for the
* {@code VideoBlurButton} component.
* {@code VideoBackgroundButton} component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _isVideoBlurred: boolean
* _isBackgroundEnabled: boolean
* }}
*/
function _mapStateToProps(state): Object {
const tracks = state['features/base/tracks'];
return {
_isVideoBlurred: Boolean(state['features/blur'].blurEnabled),
_isBackgroundEnabled: Boolean(state['features/virtual-background'].backgroundEffectEnabled),
_videoMuted: isLocalCameraTrackMuted(tracks)
};
}
export default translate(connect(_mapStateToProps)(VideoBlurButton));
export default translate(connect(_mapStateToProps)(VideoBackgroundButton));

View File

@@ -0,0 +1,118 @@
// @flow
/* eslint-disable react/jsx-no-bind, no-return-assign */
import React, { useState } from 'react';
import { Dialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
import { Icon, IconBlurBackground } from '../../base/icons';
import { connect } from '../../base/redux';
import { Tooltip } from '../../base/tooltip';
import { toggleBackgroundEffect, setVirtualBackground } from '../actions';
const images = [
{
tooltip: 'Image 1',
name: 'background-1.jpg',
id: 1,
src: 'images/virtual-background/background-1.jpg'
},
{
tooltip: 'Image 2',
name: 'background-2.jpg',
id: 2,
src: 'images/virtual-background/background-2.jpg'
},
{
tooltip: 'Image 3',
name: 'background-3.jpg',
id: 3,
src: 'images/virtual-background/background-3.jpg'
},
{
tooltip: 'Image 4',
name: 'background-4.jpg',
id: 4,
src: 'images/virtual-background/background-4.jpg'
}
];
type Props = {
/**
* The redux {@code dispatch} function.
*/
dispatch: Function,
/**
* Invoked to obtain translated strings.
*/
t: Function
};
/**
* Renders virtual background dialog.
*
* @returns {ReactElement}
*/
function VirtualBackground({ dispatch, t }: Props) {
const [ selected, setSelected ] = useState('');
const enableBlur = () => {
setSelected('blur');
dispatch(setVirtualBackground('', false));
dispatch(toggleBackgroundEffect(true));
};
const removeBackground = () => {
setSelected('none');
dispatch(setVirtualBackground('', false));
dispatch(toggleBackgroundEffect(false));
};
const addImageBackground = image => {
setSelected(image.id);
dispatch(setVirtualBackground(image.src, true));
dispatch(toggleBackgroundEffect(true));
};
return (
<Dialog
hideCancelButton = { true }
submitDisabled = { false }
titleKey = { 'virtualBackground.title' }
width = 'small'>
<div className = 'virtual-background-dialog'>
<Tooltip
content = { t('virtualBackground.removeBackground') }
position = { 'top' }>
<div
className = { selected === 'none' ? 'none-selected' : 'virtual-background-none' }
onClick = { () => removeBackground() }>
None
</div>
</Tooltip>
<Tooltip
content = { t('virtualBackground.enableBlur') }
position = { 'top' }>
<Icon
className = { selected === 'blur' ? 'blur-selected' : '' }
onClick = { () => enableBlur() }
size = { 50 }
src = { IconBlurBackground } />
</Tooltip>
{images.map((image, index) => (
<Tooltip
content = { image.tooltip }
key = { index }
position = { 'top' }>
<img
className = { selected === image.id ? 'thumbnail-selected' : 'thumbnail' }
onClick = { () => addImageBackground(image) }
onError = { event => event.target.style.display = 'none' }
src = { image.src } />
</Tooltip>
))}
</div>
</Dialog>
);
}
export default translate(connect()(VirtualBackground));

View File

@@ -0,0 +1,2 @@
export { default as VideoBackgroundButton } from './VideoBackgroundButton';
export { default as VirtualBackgroundDialog } from './VirtualBackgroundDialog';

View File

@@ -0,0 +1,22 @@
// @flow
let filterSupport;
/**
* Checks context filter support.
*
* @returns {boolean} True if the filter is supported and false if the filter is not supported by the browser.
*/
export function checkBlurSupport() {
if (typeof filterSupport === 'undefined') {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
filterSupport = typeof ctx.filter !== 'undefined';
canvas.remove();
}
return filterSupport;
}

View File

@@ -2,4 +2,4 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('features/blur');
export default getLogger('features/virtual-background');

View File

@@ -0,0 +1,38 @@
// @flow
import { ReducerRegistry } from '../base/redux';
import { BACKGROUND_ENABLED, SET_VIRTUAL_BACKGROUND } from './actionTypes';
/**
* Reduces redux actions which activate/deactivate virtual background image, or
* indicate if the virtual image background is activated/deactivated. The
* backgroundEffectEnabled flag indicate if virtual background effect is activated.
*
* @param {State} state - The current redux state.
* @param {Action} action - The redux action to reduce.
* @param {string} action.type - The type of the redux action to reduce..
* @returns {State} The next redux state that is the result of reducing the
* specified action.
*/
ReducerRegistry.register('features/virtual-background', (state = {}, action) => {
const { virtualSource, isVirtualBackground, backgroundEffectEnabled } = action;
switch (action.type) {
case SET_VIRTUAL_BACKGROUND: {
return {
...state,
virtualSource,
isVirtualBackground
};
}
case BACKGROUND_ENABLED: {
return {
...state,
backgroundEffectEnabled
};
}
}
return state;
});

View File

@@ -39,6 +39,20 @@ window.APP = {
},
getConnectionState() {
return room && room.getConnectionState();
},
muteAudio(mute) {
// Note: will have no effect if !autoCreateLocalAudio
localAudio = mute;
for (let i = 0; i < localTracks.length; i++) {
if (localTracks[i].getType() === 'audio') {
if (mute) {
localTracks[i].mute();
}
else {
localTracks[i].unmute();
}
}
}
}
},

View File

@@ -240,39 +240,6 @@ module.exports = [
performance: getPerformanceHints(128 * 1024)
}),
// Because both video-blur-effect and rnnoise-processor modules are loaded
// in a lazy manner using the loadScript function with a hard coded name,
// i.e.loadScript('libs/rnnoise-processor.min.js'), webpack dev server
// won't know how to properly load them using the default config filename
// and sourceMapFilename parameters which target libs without .min in dev
// mode. Thus we change these modules to have the same filename in both
// prod and dev mode.
Object.assign({}, config, {
entry: {
'video-blur-effect': './react/features/stream-effects/blur/index.js'
},
output: Object.assign({}, config.output, {
library: [ 'JitsiMeetJS', 'app', 'effects' ],
libraryTarget: 'window',
filename: '[name].min.js',
sourceMapFilename: '[name].min.map'
}),
performance: getPerformanceHints(1 * 1024 * 1024)
}),
Object.assign({}, config, {
entry: {
'rnnoise-processor': './react/features/stream-effects/rnnoise/index.js'
},
output: Object.assign({}, config.output, {
library: [ 'JitsiMeetJS', 'app', 'effects', 'rnnoise' ],
libraryTarget: 'window',
filename: '[name].min.js',
sourceMapFilename: '[name].min.map'
}),
performance: getPerformanceHints(30 * 1024)
}),
Object.assign({}, config, {
entry: {
'external_api': './modules/API/external/index.js'