mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-09-11 20:18:50 +00:00
Compare commits
20 Commits
4787
...
rn-fix-and
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d69584ce17 | ||
|
|
b2d271a679 | ||
|
|
7046785ca3 | ||
|
|
b817bd19d5 | ||
|
|
817d54b0b9 | ||
|
|
3f0bb6818c | ||
|
|
4fa47c8070 | ||
|
|
0dcb8a025b | ||
|
|
8defaa9aec | ||
|
|
d214079148 | ||
|
|
096ee3cb53 | ||
|
|
fd606896b8 | ||
|
|
226581a81a | ||
|
|
e1c5b1e626 | ||
|
|
831c5ba59d | ||
|
|
bad1bc91cf | ||
|
|
30d0aabaca | ||
|
|
22b6d32174 | ||
|
|
31ace267ce | ||
|
|
194d357005 |
@@ -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
|
||||
|
||||
8
Makefile
8
Makefile
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
}
|
||||
|
||||
&.settings-button-small-icon--disabled {
|
||||
background: #36383C;
|
||||
|
||||
&> svg {
|
||||
fill: #929292;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
44
css/modals/virtual-background/_virtual-background.scss
Normal file
44
css/modals/virtual-background/_virtual-background.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
BIN
images/virtual-background/background-1.jpg
Normal file
BIN
images/virtual-background/background-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 437 KiB |
BIN
images/virtual-background/background-2.jpg
Normal file
BIN
images/virtual-background/background-2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 284 KiB |
BIN
images/virtual-background/background-3.jpg
Normal file
BIN
images/virtual-background/background-3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 216 KiB |
BIN
images/virtual-background/background-4.jpg
Normal file
BIN
images/virtual-background/background-4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 341 KiB |
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]];
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
2
modules/API/external/external_api.js
vendored
2
modules/API/external/external_api.js
vendored
@@ -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;
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
15
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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'
|
||||
];
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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';
|
||||
|
||||
3
react/features/base/icons/svg/virtual-background.svg
Normal file
3
react/features/base/icons/svg/virtual-background.svg
Normal 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 |
@@ -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 ]);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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';
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default as VideoBlurButton } from './VideoBlurButton';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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.
|
||||
|
||||
@@ -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!');
|
||||
});
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
export * from './functions';
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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');
|
||||
64
react/features/stream-effects/virtual-background/index.js
Normal file
64
react/features/stream-effects/virtual-background/index.js
Normal 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);
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
23
react/features/virtual-background/actionTypes.js
Normal file
23
react/features/virtual-background/actionTypes.js
Normal 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';
|
||||
70
react/features/virtual-background/actions.js
Normal file
70
react/features/virtual-background/actions.js
Normal 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
|
||||
};
|
||||
}
|
||||
@@ -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));
|
||||
@@ -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));
|
||||
2
react/features/virtual-background/components/index.js
Normal file
2
react/features/virtual-background/components/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as VideoBackgroundButton } from './VideoBackgroundButton';
|
||||
export { default as VirtualBackgroundDialog } from './VirtualBackgroundDialog';
|
||||
22
react/features/virtual-background/functions.js
Normal file
22
react/features/virtual-background/functions.js
Normal 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;
|
||||
}
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('features/blur');
|
||||
export default getLogger('features/virtual-background');
|
||||
38
react/features/virtual-background/reducer.js
Normal file
38
react/features/virtual-background/reducer.js
Normal 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;
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user