mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-09-12 13:48:42 +00:00
Compare commits
17 Commits
4705
...
saghul-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f4c036174 | ||
|
|
8fcaea9e3d | ||
|
|
e0aab11f98 | ||
|
|
946339a52e | ||
|
|
f71e8a9982 | ||
|
|
af6080f173 | ||
|
|
b1080340ec | ||
|
|
684d121159 | ||
|
|
6740b0861e | ||
|
|
65c56669c4 | ||
|
|
2cd43ba2e4 | ||
|
|
fec2641730 | ||
|
|
04ee423257 | ||
|
|
460e137ee4 | ||
|
|
1a789130a3 | ||
|
|
16b00dc2af | ||
|
|
c3a41b8cf3 |
@@ -6,6 +6,8 @@ build/*
|
||||
flow-typed/*
|
||||
libs/*
|
||||
|
||||
react/features/stream-effects/blur/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
|
||||
# remaining JavaScript source code.
|
||||
|
||||
16
Makefile
16
Makefile
@@ -5,6 +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/
|
||||
NODE_SASS = ./node_modules/.bin/sass
|
||||
NPM = npm
|
||||
OUTPUT_DIR = .
|
||||
@@ -26,7 +28,7 @@ clean:
|
||||
rm -fr $(BUILD_DIR)
|
||||
|
||||
.NOTPARALLEL:
|
||||
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-olm deploy-css deploy-local
|
||||
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-libflac deploy-olm deploy-css deploy-local
|
||||
|
||||
deploy-init:
|
||||
rm -fr $(DEPLOY_DIR)
|
||||
@@ -82,6 +84,16 @@ deploy-rnnoise-binary:
|
||||
$(RNNOISE_WASM_DIR)/rnnoise.wasm \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-tflite:
|
||||
cp \
|
||||
$(TFLITE_WASM)/*.wasm \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-meet-models:
|
||||
cp \
|
||||
$(MEET_MODELS_DIR)/*.tflite \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-css:
|
||||
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
|
||||
$(CLEANCSS) --skip-rebase $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
|
||||
@@ -91,7 +103,7 @@ deploy-local:
|
||||
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
|
||||
|
||||
.NOTPARALLEL:
|
||||
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-olm
|
||||
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-libflac deploy-olm
|
||||
$(WEBPACK_DEV_SERVER) --detect-circular-deps
|
||||
|
||||
source-package:
|
||||
|
||||
@@ -63,7 +63,10 @@ public class BroadcastAction {
|
||||
HANG_UP("org.jitsi.meet.HANG_UP"),
|
||||
SEND_ENDPOINT_TEXT_MESSAGE("org.jitsi.meet.SEND_ENDPOINT_TEXT_MESSAGE"),
|
||||
TOGGLE_SCREEN_SHARE("org.jitsi.meet.TOGGLE_SCREEN_SHARE"),
|
||||
RETRIEVE_PARTICIPANTS_INFO("org.jitsi.meet.RETRIEVE_PARTICIPANTS_INFO");
|
||||
RETRIEVE_PARTICIPANTS_INFO("org.jitsi.meet.RETRIEVE_PARTICIPANTS_INFO"),
|
||||
OPEN_CHAT("org.jitsi.meet.OPEN_CHAT"),
|
||||
CLOSE_CHAT("org.jitsi.meet.CLOSE_CHAT"),
|
||||
SEND_CHAT_MESSAGE("org.jitsi.meet.SEND_CHAT_MESSAGE");
|
||||
|
||||
private final String action;
|
||||
|
||||
|
||||
@@ -83,7 +83,9 @@ public class BroadcastEvent {
|
||||
PARTICIPANT_LEFT("org.jitsi.meet.PARTICIPANT_LEFT"),
|
||||
ENDPOINT_TEXT_MESSAGE_RECEIVED("org.jitsi.meet.ENDPOINT_TEXT_MESSAGE_RECEIVED"),
|
||||
SCREEN_SHARE_TOGGLED("org.jitsi.meet.SCREEN_SHARE_TOGGLED"),
|
||||
PARTICIPANTS_INFO_RETRIEVED("org.jitsi.meet.PARTICIPANTS_INFO_RETRIEVED");
|
||||
PARTICIPANTS_INFO_RETRIEVED("org.jitsi.meet.PARTICIPANTS_INFO_RETRIEVED"),
|
||||
CHAT_MESSAGE_RECEIVED("org.jitsi.meet.CHAT_MESSAGE_RECEIVED"),
|
||||
CHAT_TOGGLED("org.jitsi.meet.CHAT_TOGGLED");
|
||||
|
||||
private static final String CONFERENCE_WILL_JOIN_NAME = "CONFERENCE_WILL_JOIN";
|
||||
private static final String CONFERENCE_JOINED_NAME = "CONFERENCE_JOINED";
|
||||
@@ -94,6 +96,8 @@ public class BroadcastEvent {
|
||||
private static final String ENDPOINT_TEXT_MESSAGE_RECEIVED_NAME = "ENDPOINT_TEXT_MESSAGE_RECEIVED";
|
||||
private static final String SCREEN_SHARE_TOGGLED_NAME = "SCREEN_SHARE_TOGGLED";
|
||||
private static final String PARTICIPANTS_INFO_RETRIEVED_NAME = "PARTICIPANTS_INFO_RETRIEVED";
|
||||
private static final String CHAT_MESSAGE_RECEIVED_NAME = "CHAT_MESSAGE_RECEIVED";
|
||||
private static final String CHAT_TOGGLED_NAME = "CHAT_TOGGLED";
|
||||
|
||||
private final String action;
|
||||
|
||||
@@ -134,6 +138,10 @@ public class BroadcastEvent {
|
||||
return SCREEN_SHARE_TOGGLED;
|
||||
case PARTICIPANTS_INFO_RETRIEVED_NAME:
|
||||
return PARTICIPANTS_INFO_RETRIEVED;
|
||||
case CHAT_MESSAGE_RECEIVED_NAME:
|
||||
return CHAT_MESSAGE_RECEIVED;
|
||||
case CHAT_TOGGLED_NAME:
|
||||
return CHAT_TOGGLED;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -23,4 +23,21 @@ public class BroadcastIntentHelper {
|
||||
public static Intent buildToggleScreenShareIntent() {
|
||||
return new Intent(BroadcastAction.Type.TOGGLE_SCREEN_SHARE.getAction());
|
||||
}
|
||||
|
||||
public static Intent buildOpenChatIntent(String participantId) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.OPEN_CHAT.getAction());
|
||||
intent.putExtra("to", participantId);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildCloseChatIntent() {
|
||||
return new Intent(BroadcastAction.Type.CLOSE_CHAT.getAction());
|
||||
}
|
||||
|
||||
public static Intent buildSendChatMessageIntent(String participantId, String message) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SEND_CHAT_MESSAGE.getAction());
|
||||
intent.putExtra("to", participantId);
|
||||
intent.putExtra("message", message);
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,9 @@ class ExternalAPIModule
|
||||
constants.put("SEND_ENDPOINT_TEXT_MESSAGE", BroadcastAction.Type.SEND_ENDPOINT_TEXT_MESSAGE.getAction());
|
||||
constants.put("TOGGLE_SCREEN_SHARE", BroadcastAction.Type.TOGGLE_SCREEN_SHARE.getAction());
|
||||
constants.put("RETRIEVE_PARTICIPANTS_INFO", BroadcastAction.Type.RETRIEVE_PARTICIPANTS_INFO.getAction());
|
||||
constants.put("OPEN_CHAT", BroadcastAction.Type.OPEN_CHAT.getAction());
|
||||
constants.put("CLOSE_CHAT", BroadcastAction.Type.CLOSE_CHAT.getAction());
|
||||
constants.put("SEND_CHAT_MESSAGE", BroadcastAction.Type.SEND_CHAT_MESSAGE.getAction());
|
||||
|
||||
return constants;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
top: 30px;
|
||||
right: 30px;
|
||||
transition: right 0.5s;
|
||||
z-index: $filmstripVideosZ + 1;
|
||||
z-index: $labelsZ;
|
||||
|
||||
.circular-label {
|
||||
align-items: center;
|
||||
|
||||
@@ -47,4 +47,5 @@
|
||||
border-radius: 3px;
|
||||
margin: -16px -24px;
|
||||
padding: 16px 24px;
|
||||
z-index: $popoverZ;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
1px 0px 1px rgba(0,0,0,0.3),
|
||||
0px 0px 1px rgba(0,0,0,0.3);
|
||||
transform: translateX(-50%);
|
||||
z-index: $filmstripVideosZ + 1;
|
||||
z-index: $subtitlesZ;
|
||||
|
||||
span {
|
||||
background: black;
|
||||
|
||||
@@ -114,7 +114,10 @@ $zindex1: 1;
|
||||
$zindex2: 2;
|
||||
$zindex3: 3;
|
||||
$toolbarBackgroundZ: 4;
|
||||
$filmstripVideosZ: 5;
|
||||
$labelsZ: 5;
|
||||
$filmstripVideosZ: 6;
|
||||
$subtitlesZ: 7;
|
||||
$popoverZ: 8;
|
||||
$zindex10: 10;
|
||||
$reloadZ: 20;
|
||||
$poweredByZ: 100;
|
||||
@@ -126,7 +129,6 @@ $tooltipsZ: 401;
|
||||
$dropdownMaskZ: 900;
|
||||
$dropdownZ: 901;
|
||||
$centeredVideoLabelZ: 1010;
|
||||
$popoverZ: 1015;
|
||||
$overlayZ: 1016;
|
||||
|
||||
|
||||
|
||||
@@ -480,14 +480,6 @@
|
||||
z-index: $reloadZ; /*The reload button should appear on top of the header!*/
|
||||
}
|
||||
|
||||
.audiolevel {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
z-index: $zindex0;
|
||||
border-radius:1px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#dominantSpeaker {
|
||||
visibility: hidden;
|
||||
width: 300px;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
}
|
||||
|
||||
.horizontal-filmstrip .filmstrip {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 10px 5px;
|
||||
|
||||
@@ -42,10 +42,9 @@
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: $filmstripVideosZ;
|
||||
|
||||
@media (min-width: 581px) {
|
||||
&.shift-right {
|
||||
|
||||
@@ -22,11 +22,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
#remoteConnectionMessage,
|
||||
.watermark {
|
||||
z-index: $filmstripVideosZ + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The follow styling uses !important to override inline styles set with
|
||||
* javascript.
|
||||
|
||||
@@ -7,10 +7,6 @@
|
||||
text-align: center;
|
||||
z-index: $zindex2;
|
||||
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0));
|
||||
|
||||
&.elevated {
|
||||
z-index: $filmstripVideosZ + 1;
|
||||
}
|
||||
}
|
||||
|
||||
&-header {
|
||||
@@ -98,11 +94,11 @@
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
&:hover > div:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
|
||||
& > :not(:last-child) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
@@ -111,7 +111,3 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#videoResolutionLabel {
|
||||
z-index: $zindex3 + 1;
|
||||
}
|
||||
|
||||
@@ -123,6 +123,14 @@
|
||||
NSLog(@"%@%@", @"Screen share toggled: ", data);
|
||||
}
|
||||
|
||||
- (void)chatMessageReceived:(NSDictionary *)data {
|
||||
NSLog(@"%@%@", @"Chat message received: ", data);
|
||||
}
|
||||
|
||||
- (void)chatToggled:(NSDictionary *)data {
|
||||
NSLog(@"%@%@", @"Chat toggled: ", data);
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (void)terminate {
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
@interface ExternalAPI : RCTEventEmitter<RCTBridgeModule>
|
||||
|
||||
- (void)sendHangUp;
|
||||
- (void)sendSetAudioMuted: (BOOL)muted;
|
||||
- (void)sendSetAudioMuted:(BOOL)muted;
|
||||
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message;
|
||||
- (void)toggleScreenShare;
|
||||
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completion;
|
||||
- (void)openChat:(NSString*)to;
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message;
|
||||
|
||||
@end
|
||||
|
||||
@@ -23,6 +23,9 @@ static NSString * const setAudioMutedAction = @"org.jitsi.meet.SET_AUDIO_MUTED";
|
||||
static NSString * const sendEndpointTextMessageAction = @"org.jitsi.meet.SEND_ENDPOINT_TEXT_MESSAGE";
|
||||
static NSString * const toggleScreenShareAction = @"org.jitsi.meet.TOGGLE_SCREEN_SHARE";
|
||||
static NSString * const retrieveParticipantsInfoAction = @"org.jitsi.meet.RETRIEVE_PARTICIPANTS_INFO";
|
||||
static NSString * const openChatAction = @"org.jitsi.meet.OPEN_CHAT";
|
||||
static NSString * const closeChatAction = @"org.jitsi.meet.CLOSE_CHAT";
|
||||
static NSString * const sendChatMessageAction = @"org.jitsi.meet.SEND_CHAT_MESSAGE";
|
||||
|
||||
@implementation ExternalAPI
|
||||
|
||||
@@ -41,7 +44,10 @@ RCT_EXPORT_MODULE();
|
||||
@"SET_AUDIO_MUTED" : setAudioMutedAction,
|
||||
@"SEND_ENDPOINT_TEXT_MESSAGE": sendEndpointTextMessageAction,
|
||||
@"TOGGLE_SCREEN_SHARE": toggleScreenShareAction,
|
||||
@"RETRIEVE_PARTICIPANTS_INFO": retrieveParticipantsInfoAction
|
||||
@"RETRIEVE_PARTICIPANTS_INFO": retrieveParticipantsInfoAction,
|
||||
@"OPEN_CHAT": openChatAction,
|
||||
@"CLOSE_CHAT": closeChatAction,
|
||||
@"SEND_CHAT_MESSAGE": sendChatMessageAction
|
||||
};
|
||||
};
|
||||
|
||||
@@ -61,7 +67,11 @@ RCT_EXPORT_MODULE();
|
||||
setAudioMutedAction,
|
||||
sendEndpointTextMessageAction,
|
||||
toggleScreenShareAction,
|
||||
retrieveParticipantsInfoAction];
|
||||
retrieveParticipantsInfoAction,
|
||||
openChatAction,
|
||||
closeChatAction,
|
||||
sendChatMessageAction
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,10 +154,9 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
}
|
||||
|
||||
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message {
|
||||
NSDictionary *data = @{
|
||||
@"to": to,
|
||||
@"message": message
|
||||
};
|
||||
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
|
||||
data[@"to"] = to;
|
||||
data[@"message"] = message;
|
||||
|
||||
[self sendEventWithName:sendEndpointTextMessageAction body:data];
|
||||
}
|
||||
@@ -164,4 +173,24 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
|
||||
[self sendEventWithName:retrieveParticipantsInfoAction body:data];
|
||||
}
|
||||
|
||||
- (void)openChat:(NSString*)to {
|
||||
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
|
||||
data[@"to"] = to;
|
||||
|
||||
[self sendEventWithName:openChatAction body:data];
|
||||
}
|
||||
|
||||
- (void)closeChat {
|
||||
[self sendEventWithName:closeChatAction body:nil];
|
||||
}
|
||||
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message {
|
||||
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
|
||||
data[@"to"] = to;
|
||||
data[@"message"] = message;
|
||||
|
||||
[self sendEventWithName:sendChatMessageAction body:data];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -36,15 +36,13 @@
|
||||
* Leaves the currently active conference.
|
||||
*/
|
||||
- (void)leave;
|
||||
|
||||
- (void)hangUp;
|
||||
|
||||
- (void)setAudioMuted:(BOOL)muted;
|
||||
|
||||
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message;
|
||||
|
||||
- (void)toggleScreenShare;
|
||||
|
||||
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completionHandler;
|
||||
- (void)openChat:(NSString*)to;
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message;
|
||||
|
||||
@end
|
||||
|
||||
@@ -140,6 +140,21 @@ static void initializeViewsMap() {
|
||||
[externalAPI retrieveParticipantsInfo:completionHandler];
|
||||
}
|
||||
|
||||
- (void)openChat:(NSString*)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI openChat:to];
|
||||
}
|
||||
|
||||
- (void)closeChat {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI closeChat];
|
||||
}
|
||||
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendChatMessage:to :message];
|
||||
}
|
||||
|
||||
#pragma mark Private methods
|
||||
|
||||
/**
|
||||
|
||||
@@ -90,4 +90,18 @@
|
||||
*/
|
||||
- (void)screenShareToggled:(NSDictionary *)data;
|
||||
|
||||
/**
|
||||
* Called when a chat message is received.
|
||||
*
|
||||
* The `data` dictionary contains `message`, `senderId` and `isPrivate` keys.
|
||||
*/
|
||||
- (void)chatMessaageReceived:(NSDictionary *)data;
|
||||
|
||||
/**
|
||||
* Called when the chat dialog is displayed/hidden.
|
||||
*
|
||||
* The `data` dictionary contains a `isOpen` key.
|
||||
*/
|
||||
- (void)chatToggled:(NSDictionary *)data;
|
||||
|
||||
@end
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
"cameraNotSendingData": "Non possiamo accedere alla tua videocamera. Controlla che non sia già usata da un'altra applicazione, seleziona un altro dispositivo dalle impostazioni o prova a ricaricare l'applicazione.",
|
||||
"cameraNotSendingDataTitle": "Impossibile accedere alla videocamera",
|
||||
"cameraPermissionDeniedError": "Non hai concesso il permesso di usare la videocamera. Potrai partecipare comunque alla riunione ma gli altri non potranno vederti. Usa il pulsante a forma di videocamera nella barra degli indirizzi per risolvere il problema.",
|
||||
"cameraTimeoutError": "Impossibile avviare la sorgente video. Tempo di attesa scaduto.",
|
||||
"cameraUnknownError": "Impossibile usare la videocamera per un motivo sconosciuto.",
|
||||
"cameraUnsupportedResolutionError": "La tua videocamera non supporta la risoluzione richiesta.",
|
||||
"Cancel": "Annulla",
|
||||
@@ -233,6 +234,7 @@
|
||||
"micNotSendingData": "Apri le impostazioni del computer per togliere il «muto» al microfono e imposta il volume.",
|
||||
"micNotSendingDataTitle": "Il microfono è muto per impostazione di sistema",
|
||||
"micPermissionDeniedError": "Non hai concesso il permesso di usare il microfono. Puoi comunque partecipare alla riunione ma gli altri non potranno sentirti. Usa il bottone a forma di telecamera nella barra degli indirizzi per cambiare impostazioni.",
|
||||
"micTimeoutError": "Impossibile avviare la fonte audio. Tempo di attesa scaduto.",
|
||||
"micUnknownError": "Impossibile usare il microfono per un motivo sconosciuto.",
|
||||
"muteEveryoneElseDialog": "Una volta zittiti, non potrai riattivargli i microfoni, ma loro potranno farlo in qualsiasi momento.",
|
||||
"muteEveryoneElseTitle": "Zittisco tutti eccetto {{whom}}?",
|
||||
@@ -280,6 +282,7 @@
|
||||
"sendPrivateMessageTitle": "Invio privatamente?",
|
||||
"serviceUnavailable": "Servizio non disponibile",
|
||||
"sessTerminated": "Chiamata terminata",
|
||||
"sessionRestarted": "Chiamata riavviata automaticamente",
|
||||
"Share": "Condividi",
|
||||
"shareVideoLinkError": "Fornire un link youtube corretto.",
|
||||
"shareVideoTitle": "Condividi un video",
|
||||
@@ -734,7 +737,7 @@
|
||||
},
|
||||
"addPeople": "Aggiungi persone alla chiamata",
|
||||
"audioOnlyOff": "Disabilita modalità per banda limitata",
|
||||
"audioOnlyOn": "Abilita modalità per banda limitatao",
|
||||
"audioOnlyOn": "Abilita modalità per banda limitata",
|
||||
"audioRoute": "Scegli l'uscita audio",
|
||||
"authenticate": "Autenticazione",
|
||||
"callQuality": "Imposta qualità video",
|
||||
|
||||
@@ -14,8 +14,9 @@ import {
|
||||
} from '../../react/features/base/conference';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
|
||||
import JitsiMeetJS, { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
|
||||
import { pinParticipant, getParticipantById } from '../../react/features/base/participants';
|
||||
import { pinParticipant, getParticipantById, kickParticipant } from '../../react/features/base/participants';
|
||||
import { setPrivateMessageRecipient } from '../../react/features/chat/actions';
|
||||
import { openChat } from '../../react/features/chat/actions.web';
|
||||
import {
|
||||
processExternalDeviceRequest
|
||||
} from '../../react/features/device-selection/functions';
|
||||
@@ -342,13 +343,16 @@ function initCommands() {
|
||||
if (!isChatOpen) {
|
||||
APP.UI.toggleChat();
|
||||
}
|
||||
APP.store.dispatch(setPrivateMessageRecipient(participant));
|
||||
APP.store.dispatch(openChat(participant));
|
||||
} else {
|
||||
logger.error('No participant found for the given participantId');
|
||||
}
|
||||
},
|
||||
'cancel-private-chat': () => {
|
||||
APP.store.dispatch(setPrivateMessageRecipient());
|
||||
},
|
||||
'kick-participant': participantId => {
|
||||
APP.store.dispatch(kickParticipant(participantId));
|
||||
}
|
||||
};
|
||||
transport.on('event', ({ data, name }) => {
|
||||
|
||||
1
modules/API/external/external_api.js
vendored
1
modules/API/external/external_api.js
vendored
@@ -35,6 +35,7 @@ const commands = {
|
||||
toggleLobby: 'toggle-lobby',
|
||||
hangup: 'video-hangup',
|
||||
intiatePrivateChat: 'initiate-private-chat',
|
||||
kickParticipant: 'kick-participant',
|
||||
muteEveryone: 'mute-everyone',
|
||||
password: 'password',
|
||||
pinParticipant: 'pin-participant',
|
||||
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -10343,8 +10343,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#84357ce1a8d06c67a502709877130520aaf71592",
|
||||
"from": "github:jitsi/lib-jitsi-meet#84357ce1a8d06c67a502709877130520aaf71592",
|
||||
"version": "github:jitsi/lib-jitsi-meet#c534f748849a308d08b06e306f5a66709ccae056",
|
||||
"from": "github:jitsi/lib-jitsi-meet#c534f748849a308d08b06e306f5a66709ccae056",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "1.0.2",
|
||||
"@jitsi/sdp-interop": "1.0.3",
|
||||
@@ -15491,11 +15491,6 @@
|
||||
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz",
|
||||
"integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA=="
|
||||
},
|
||||
"stackblur-canvas": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.3.0.tgz",
|
||||
"integrity": "sha512-3ZHJv+43D8YttgumssIxkfs3hBXW7XaMS5Ux65fOBhKDYMjbG5hF8Ey8a90RiiJ58aQnAhWbGilPzZ9rkIlWgQ=="
|
||||
},
|
||||
"stacktrace-parser": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.8.tgz",
|
||||
@@ -17071,6 +17066,11 @@
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"wasm-check": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wasm-check/-/wasm-check-2.0.1.tgz",
|
||||
"integrity": "sha512-5otny2JrfRNKIc+zi1YSOrNxXe47trEQbpY6g/MtHrFwLumKSJyAIobGXH1tlEBezE95eIsmDokBbUZtIZTvvA=="
|
||||
},
|
||||
"watchpack": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz",
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#84357ce1a8d06c67a502709877130520aaf71592",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#c534f748849a308d08b06e306f5a66709ccae056",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.19",
|
||||
"moment": "2.19.4",
|
||||
@@ -95,10 +95,10 @@
|
||||
"redux-thunk": "2.2.0",
|
||||
"rnnoise-wasm": "github:jitsi/rnnoise-wasm#566a16885897704d6e6d67a1d5ac5d39781db2af",
|
||||
"rtcstats": "github:jitsi/rtcstats#v6.2.0",
|
||||
"stackblur-canvas": "2.3.0",
|
||||
"styled-components": "3.4.9",
|
||||
"util": "0.12.1",
|
||||
"uuid": "3.1.0",
|
||||
"wasm-check": "2.0.1",
|
||||
"windows-iana": "^3.1.0",
|
||||
"xmldom": "0.1.27",
|
||||
"zxcvbn": "4.4.2"
|
||||
|
||||
@@ -56,6 +56,12 @@ export const CHAT_ENABLED = 'chat.enabled';
|
||||
*/
|
||||
export const FILMSTRIP_ENABLED = 'filmstrip.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if the Help button should be enabled.
|
||||
* Default: enabled (true).
|
||||
*/
|
||||
export const HELP_BUTTON_ENABLED = 'help.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if invite functionality should be enabled.
|
||||
* Default: enabled (true).
|
||||
|
||||
@@ -15,16 +15,13 @@ export function preloadImage(src: string | Object): Promise<string> {
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(src, { referrer: '' })
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
resolve(src);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
reject(e);
|
||||
});
|
||||
const image = document.createElement('img');
|
||||
|
||||
image.onload = () => resolve(src);
|
||||
image.onerror = reject;
|
||||
|
||||
// $FlowExpectedError
|
||||
image.referrerPolicy = 'no-referrer';
|
||||
image.src = src;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@ export function getTrackByMediaTypeAndParticipant(
|
||||
mediaType,
|
||||
participantId) {
|
||||
return tracks.find(
|
||||
t => t.participantId === participantId && t.mediaType === mediaType
|
||||
t => Boolean(t.jitsiTrack) && t.participantId === participantId && t.mediaType === mediaType
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,24 @@ export const ADD_MESSAGE = 'ADD_MESSAGE';
|
||||
*/
|
||||
export const CLEAR_MESSAGES = 'CLEAR_MESSAGES';
|
||||
|
||||
/**
|
||||
* The type of the action which signals the cancelation the chat panel.
|
||||
*
|
||||
* {
|
||||
* type: CLOSE_CHAT
|
||||
* }
|
||||
*/
|
||||
export const CLOSE_CHAT = 'CLOSE_CHAT';
|
||||
|
||||
/**
|
||||
* The type of the action which signals to display the chat panel.
|
||||
*
|
||||
* {
|
||||
* type: OPEN_CHAT
|
||||
* }
|
||||
*/
|
||||
export const OPEN_CHAT = 'OPEN_CHAT';
|
||||
|
||||
/**
|
||||
* The type of the action which signals a send a chat message to everyone in the
|
||||
* conference.
|
||||
@@ -46,12 +64,3 @@ export const SEND_MESSAGE = 'SEND_MESSAGE';
|
||||
* }
|
||||
*/
|
||||
export const SET_PRIVATE_MESSAGE_RECIPIENT = 'SET_PRIVATE_MESSAGE_RECIPIENT';
|
||||
|
||||
/**
|
||||
* The type of the action which signals to toggle the display of the chat panel.
|
||||
*
|
||||
* {
|
||||
* type: TOGGLE_CHAT
|
||||
* }
|
||||
*/
|
||||
export const TOGGLE_CHAT = 'TOGGLE_CHAT';
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import {
|
||||
ADD_MESSAGE,
|
||||
CLEAR_MESSAGES,
|
||||
CLOSE_CHAT,
|
||||
SEND_MESSAGE,
|
||||
SET_PRIVATE_MESSAGE_RECIPIENT
|
||||
} from './actionTypes';
|
||||
@@ -49,6 +50,19 @@ export function clearMessages() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to signal the closing of the chat dialog.
|
||||
*
|
||||
* @returns {{
|
||||
* type: CLOSE_CHAT
|
||||
* }}
|
||||
*/
|
||||
export function closeChat() {
|
||||
return {
|
||||
type: CLOSE_CHAT
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a chat message to everyone in the conference.
|
||||
*
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
// @flow
|
||||
|
||||
import { TOGGLE_CHAT } from './actionTypes';
|
||||
import { OPEN_CHAT } from './actionTypes';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Toggles display of the chat panel.
|
||||
* Displays the chat panel.
|
||||
*
|
||||
* @returns {Function}
|
||||
* @param {Object} participant - The recipient for the private chat.
|
||||
*
|
||||
* @returns {{
|
||||
* participant: Participant,
|
||||
* type: OPEN_CHAT
|
||||
* }}
|
||||
*/
|
||||
export function toggleChat() {
|
||||
return function(dispatch: (Object) => Object) {
|
||||
dispatch({ type: TOGGLE_CHAT });
|
||||
export function openChat(participant: Object) {
|
||||
return {
|
||||
participant,
|
||||
type: OPEN_CHAT
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,20 +1,44 @@
|
||||
// @flow
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
|
||||
|
||||
import { TOGGLE_CHAT } from './actionTypes';
|
||||
import { OPEN_CHAT } from './actionTypes';
|
||||
import { closeChat } from './actions.any';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Toggles display of the chat side panel while also taking window
|
||||
* resize into account.
|
||||
* Displays the chat panel.
|
||||
*
|
||||
* @param {Object} participant - The recipient for the private chat.
|
||||
* @returns {{
|
||||
* participant: Participant,
|
||||
* type: OPEN_CHAT
|
||||
* }}
|
||||
*/
|
||||
export function openChat(participant: Object) {
|
||||
return function(dispatch: (Object) => Object) {
|
||||
dispatch({ participant,
|
||||
type: OPEN_CHAT });
|
||||
VideoLayout.onResize();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles display of the chat panel.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function toggleChat() {
|
||||
return function(dispatch: (Object) => Object) {
|
||||
dispatch({ type: TOGGLE_CHAT });
|
||||
VideoLayout.onResize();
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const isOpen = getState()['features/chat'].isOpen;
|
||||
|
||||
if (isOpen) {
|
||||
dispatch(closeChat());
|
||||
} else {
|
||||
dispatch(openChat());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { Dispatch } from 'redux';
|
||||
|
||||
import { isMobileBrowser } from '../../base/environment/utils';
|
||||
import { getLocalParticipant } from '../../base/participants';
|
||||
import { sendMessage, toggleChat } from '../actions';
|
||||
import { sendMessage } from '../actions';
|
||||
import { DESKTOP_SMALL_WIDTH_THRESHOLD, MOBILE_SMALL_WIDTH_THRESHOLD } from '../constants';
|
||||
|
||||
/**
|
||||
@@ -59,41 +59,34 @@ export type Props = {
|
||||
/**
|
||||
* Implements an abstract chat panel.
|
||||
*/
|
||||
export default class AbstractChat<P: Props> extends Component<P> {}
|
||||
export default class AbstractChat<P: Props> extends Component<P> {
|
||||
|
||||
/**
|
||||
* Maps redux actions to the props of the component.
|
||||
*
|
||||
* @param {Function} dispatch - The redux action {@code dispatch} function.
|
||||
* @returns {{
|
||||
* _onSendMessage: Function,
|
||||
* _onToggleChat: Function
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
export function _mapDispatchToProps(dispatch: Dispatch<any>) {
|
||||
return {
|
||||
/**
|
||||
* Toggles the chat window.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onToggleChat() {
|
||||
dispatch(toggleChat());
|
||||
},
|
||||
/**
|
||||
* Initializes a new {@code AbstractChat} instance.
|
||||
*
|
||||
* @param {Props} props - The React {@code Component} props to initialize
|
||||
* the new {@code AbstractChat} instance with.
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
/**
|
||||
* Sends a text message.
|
||||
*
|
||||
* @private
|
||||
* @param {string} text - The text message to be sent.
|
||||
* @returns {void}
|
||||
* @type {Function}
|
||||
*/
|
||||
_onSendMessage(text: string) {
|
||||
dispatch(sendMessage(text));
|
||||
}
|
||||
};
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onSendMessage = this._onSendMessage.bind(this);
|
||||
}
|
||||
|
||||
_onSendMessage: (string) => void;
|
||||
|
||||
/**
|
||||
* Sends a text message.
|
||||
*
|
||||
* @private
|
||||
* @param {string} text - The text message to be sent.
|
||||
* @returns {void}
|
||||
* @type {Function}
|
||||
*/
|
||||
_onSendMessage(text: string) {
|
||||
this.props.dispatch(sendMessage(text));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@ import { IconMessage, IconReply } from '../../base/icons';
|
||||
import { getParticipantById } from '../../base/participants';
|
||||
import { connect } from '../../base/redux';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
|
||||
import { setPrivateMessageRecipient } from '../actions';
|
||||
import { openChat } from '../actions';
|
||||
|
||||
export type Props = AbstractButtonProps & {
|
||||
|
||||
@@ -24,15 +24,15 @@ export type Props = AbstractButtonProps & {
|
||||
*/
|
||||
t: Function,
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* The participant object retreived from Redux.
|
||||
*/
|
||||
_participant: Object,
|
||||
|
||||
/**
|
||||
* Function to dispatch the result of the participant selection to send a private message.
|
||||
*/
|
||||
_setPrivateMessageRecipient: Function
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -51,9 +51,9 @@ class PrivateMessageButton extends AbstractButton<Props, any> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { _participant, _setPrivateMessageRecipient } = this.props;
|
||||
const { dispatch, _participant } = this.props;
|
||||
|
||||
_setPrivateMessageRecipient(_participant);
|
||||
dispatch(openChat(_participant));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,20 +69,6 @@ class PrivateMessageButton extends AbstractButton<Props, any> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the props of this component to Redux actions.
|
||||
*
|
||||
* @param {Function} dispatch - The Redux dispatch function.
|
||||
* @returns {Props}
|
||||
*/
|
||||
export function _mapDispatchToProps(dispatch: Function): $Shape<Props> {
|
||||
return {
|
||||
_setPrivateMessageRecipient: participant => {
|
||||
dispatch(setPrivateMessageRecipient(participant));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux store to the props of this component.
|
||||
*
|
||||
@@ -96,4 +82,4 @@ export function _mapStateToProps(state: Object, ownProps: Props): $Shape<Props>
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(PrivateMessageButton));
|
||||
export default translate(connect(_mapStateToProps)(PrivateMessageButton));
|
||||
|
||||
@@ -5,9 +5,9 @@ import React from 'react';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { JitsiModal } from '../../../base/modal';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { closeChat } from '../../actions.any';
|
||||
import { CHAT_VIEW_MODAL_ID } from '../../constants';
|
||||
import AbstractChat, {
|
||||
_mapDispatchToProps,
|
||||
_mapStateToProps,
|
||||
type Props
|
||||
} from '../AbstractChat';
|
||||
@@ -48,11 +48,13 @@ class Chat extends AbstractChat<Props> {
|
||||
|
||||
<MessageContainer messages = { this.props._messages } />
|
||||
<MessageRecipient />
|
||||
<ChatInputBar onSend = { this.props._onSendMessage } />
|
||||
<ChatInputBar onSend = { this._onSendMessage } />
|
||||
</JitsiModal>
|
||||
);
|
||||
}
|
||||
|
||||
_onSendMessage: (string) => void;
|
||||
|
||||
_onClose: () => boolean
|
||||
|
||||
/**
|
||||
@@ -61,10 +63,10 @@ class Chat extends AbstractChat<Props> {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_onClose() {
|
||||
this.props._onToggleChat();
|
||||
this.props.dispatch(closeChat());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat));
|
||||
export default translate(connect(_mapStateToProps)(Chat));
|
||||
|
||||
@@ -2,41 +2,22 @@
|
||||
|
||||
import { CHAT_ENABLED, getFeatureFlag } from '../../../base/flags';
|
||||
import { IconChat, IconChatUnread } from '../../../base/icons';
|
||||
import { setActiveModalId } from '../../../base/modal';
|
||||
import { getLocalParticipant } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import {
|
||||
AbstractButton,
|
||||
type AbstractButtonProps
|
||||
} from '../../../base/toolbox/components';
|
||||
import { openDisplayNamePrompt } from '../../../display-name';
|
||||
import { CHAT_VIEW_MODAL_ID } from '../../constants';
|
||||
import { openChat } from '../../actions.native';
|
||||
import { getUnreadCount } from '../../functions';
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* Function to display chat.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
_displayChat: Function,
|
||||
|
||||
/**
|
||||
* Function to diaply the name prompt before displaying the chat
|
||||
* window, if the user has no display name set.
|
||||
*/
|
||||
_displayNameInputDialog: Function,
|
||||
|
||||
/**
|
||||
* Whether or not to block chat access with a nickname input form.
|
||||
*/
|
||||
_showNamePrompt: boolean,
|
||||
|
||||
/**
|
||||
* The unread message count.
|
||||
*/
|
||||
_unreadMessageCount: number
|
||||
_unreadMessageCount: number,
|
||||
|
||||
dispatch: Function
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -55,13 +36,7 @@ class ChatButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
if (this.props._showNamePrompt) {
|
||||
this.props._displayNameInputDialog(() => {
|
||||
this.props._displayChat();
|
||||
});
|
||||
} else {
|
||||
this.props._displayChat();
|
||||
}
|
||||
this.props.dispatch(openChat());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,41 +50,6 @@ class ChatButton extends AbstractButton<Props, *> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps redux actions to the props of the component.
|
||||
*
|
||||
* @param {Function} dispatch - The redux action {@code dispatch} function.
|
||||
* @returns {{
|
||||
* _displayChat,
|
||||
* _displayNameInputDialog
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _mapDispatchToProps(dispatch: Function) {
|
||||
return {
|
||||
/**
|
||||
* Launches native invite dialog.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_displayChat() {
|
||||
dispatch(setActiveModalId(CHAT_VIEW_MODAL_ID));
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays a display name prompt.
|
||||
*
|
||||
* @param {Function} onPostSubmit - The function to invoke after a
|
||||
* succesfulsetting of the display name.
|
||||
* @returns {void}
|
||||
*/
|
||||
_displayNameInputDialog(onPostSubmit) {
|
||||
dispatch(openDisplayNamePrompt(onPostSubmit));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the redux state to the component's props.
|
||||
*
|
||||
@@ -118,15 +58,13 @@ function _mapDispatchToProps(dispatch: Function) {
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
|
||||
const { visible = enabled } = ownProps;
|
||||
|
||||
return {
|
||||
_showNamePrompt: !localParticipant.name,
|
||||
_unreadMessageCount: getUnreadCount(state),
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps, _mapDispatchToProps)(ChatButton);
|
||||
export default connect(_mapStateToProps)(ChatButton);
|
||||
|
||||
@@ -4,8 +4,8 @@ import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { toggleChat } from '../../actions.web';
|
||||
import AbstractChat, {
|
||||
_mapDispatchToProps,
|
||||
_mapStateToProps,
|
||||
type Props
|
||||
} from '../AbstractChat';
|
||||
@@ -49,9 +49,8 @@ class Chat extends AbstractChat<Props> {
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._renderPanelContent = this._renderPanelContent.bind(this);
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onChatInputResize = this._onChatInputResize.bind(this);
|
||||
this._onToggleChat = this._onToggleChat.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,7 +118,7 @@ class Chat extends AbstractChat<Props> {
|
||||
<MessageRecipient />
|
||||
<ChatInput
|
||||
onResize = { this._onChatInputResize }
|
||||
onSend = { this.props._onSendMessage } />
|
||||
onSend = { this._onSendMessage } />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -135,7 +134,7 @@ class Chat extends AbstractChat<Props> {
|
||||
return (
|
||||
<Header
|
||||
className = 'chat-header'
|
||||
onCancel = { this.props._onToggleChat } />
|
||||
onCancel = { this._onToggleChat } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -197,6 +196,20 @@ class Chat extends AbstractChat<Props> {
|
||||
this._messageContainerRef.current.scrollToBottom(withAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
_onSendMessage: (string) => void;
|
||||
|
||||
_onToggleChat: () => void;
|
||||
|
||||
/**
|
||||
* Toggles the chat window.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onToggleChat() {
|
||||
this.props.dispatch(toggleChat());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat));
|
||||
export default translate(connect(_mapStateToProps)(Chat));
|
||||
|
||||
@@ -5,7 +5,7 @@ import React from 'react';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconClose } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { toggleChat } from '../../../chat';
|
||||
import { closeChat } from '../../actions.any';
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -42,6 +42,6 @@ function Header({ onCancel, className, t }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = { onCancel: toggleChat };
|
||||
const mapDispatchToProps = { onCancel: closeChat };
|
||||
|
||||
export default translate(connect(null, mapDispatchToProps)(Header));
|
||||
|
||||
@@ -18,10 +18,12 @@ import {
|
||||
} from '../base/participants';
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
|
||||
import { playSound, registerSound, unregisterSound } from '../base/sounds';
|
||||
import { openDisplayNamePrompt } from '../display-name';
|
||||
import { showToolbox } from '../toolbox/actions';
|
||||
|
||||
import { ADD_MESSAGE, TOGGLE_CHAT, SEND_MESSAGE, SET_PRIVATE_MESSAGE_RECIPIENT } from './actionTypes';
|
||||
import { addMessage, clearMessages, toggleChat } from './actions';
|
||||
import { ADD_MESSAGE, SEND_MESSAGE, OPEN_CHAT, CLOSE_CHAT } from './actionTypes';
|
||||
import { addMessage, clearMessages } from './actions';
|
||||
import { closeChat } from './actions.any';
|
||||
import { ChatPrivacyDialog } from './components';
|
||||
import {
|
||||
CHAT_VIEW_MODAL_ID,
|
||||
@@ -52,6 +54,7 @@ const PRIVACY_NOTICE_TIMEOUT = 20 * 1000;
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const { dispatch, getState } = store;
|
||||
const localParticipant = getLocalParticipant(getState());
|
||||
let isOpen, unreadCount;
|
||||
|
||||
switch (action.type) {
|
||||
@@ -63,14 +66,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
APP.API.notifyChatUpdated(unreadCount, isOpen);
|
||||
}
|
||||
break;
|
||||
case TOGGLE_CHAT:
|
||||
unreadCount = 0;
|
||||
isOpen = !getState()['features/chat'].isOpen;
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyChatUpdated(unreadCount, isOpen);
|
||||
}
|
||||
break;
|
||||
case APP_WILL_MOUNT:
|
||||
dispatch(
|
||||
registerSound(INCOMING_MSG_SOUND_ID, INCOMING_MSG_SOUND_FILE));
|
||||
@@ -84,6 +80,34 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
_addChatMsgListener(action.conference, store);
|
||||
break;
|
||||
|
||||
case OPEN_CHAT:
|
||||
if (localParticipant.name) {
|
||||
dispatch(setActiveModalId(CHAT_VIEW_MODAL_ID));
|
||||
_maybeFocusField();
|
||||
} else {
|
||||
dispatch(openDisplayNamePrompt(() => {
|
||||
dispatch(setActiveModalId(CHAT_VIEW_MODAL_ID));
|
||||
_maybeFocusField();
|
||||
}));
|
||||
}
|
||||
|
||||
unreadCount = 0;
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyChatUpdated(unreadCount, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case CLOSE_CHAT:
|
||||
unreadCount = 0;
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyChatUpdated(unreadCount, true);
|
||||
}
|
||||
|
||||
dispatch(setActiveModalId());
|
||||
break;
|
||||
|
||||
case SEND_MESSAGE: {
|
||||
const state = store.getState();
|
||||
const { conference } = state['features/base/conference'];
|
||||
@@ -117,12 +141,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_PRIVATE_MESSAGE_RECIPIENT: {
|
||||
Boolean(action.participant) && dispatch(setActiveModalId(CHAT_VIEW_MODAL_ID));
|
||||
_maybeFocusField();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
@@ -141,7 +159,7 @@ StateListenerRegistry.register(
|
||||
|
||||
if (getState()['features/chat'].isOpen) {
|
||||
// Closes the chat if it's left open.
|
||||
dispatch(toggleChat());
|
||||
dispatch(closeChat());
|
||||
}
|
||||
|
||||
// Clear chat messages.
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
// @flow
|
||||
|
||||
import { SET_ACTIVE_MODAL_ID } from '../base/modal';
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
ADD_MESSAGE,
|
||||
CLEAR_MESSAGES,
|
||||
SET_PRIVATE_MESSAGE_RECIPIENT,
|
||||
TOGGLE_CHAT
|
||||
CLOSE_CHAT,
|
||||
OPEN_CHAT,
|
||||
SET_PRIVATE_MESSAGE_RECIPIENT
|
||||
} from './actionTypes';
|
||||
import { CHAT_VIEW_MODAL_ID } from './constants';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
isOpen: false,
|
||||
@@ -58,38 +57,28 @@ ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
|
||||
messages: []
|
||||
};
|
||||
|
||||
case SET_ACTIVE_MODAL_ID:
|
||||
if (action.activeModalId === CHAT_VIEW_MODAL_ID) {
|
||||
return updateChatState(state);
|
||||
}
|
||||
|
||||
break;
|
||||
case SET_PRIVATE_MESSAGE_RECIPIENT:
|
||||
return {
|
||||
...state,
|
||||
isOpen: Boolean(action.participant) || state.isOpen,
|
||||
privateMessageRecipient: action.participant
|
||||
};
|
||||
|
||||
case TOGGLE_CHAT:
|
||||
return updateChatState(state);
|
||||
case OPEN_CHAT:
|
||||
return {
|
||||
...state,
|
||||
isOpen: true,
|
||||
privateMessageRecipient: action.participant
|
||||
};
|
||||
|
||||
case CLOSE_CHAT:
|
||||
return {
|
||||
...state,
|
||||
isOpen: false,
|
||||
lastReadMessage: state.messages[
|
||||
navigator.product === 'ReactNative' ? 0 : state.messages.length - 1],
|
||||
privateMessageRecipient: action.participant
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
|
||||
/**
|
||||
* Updates the chat status on opening the chat view.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function updateChatState(state) {
|
||||
return {
|
||||
...state,
|
||||
isOpen: !state.isOpen,
|
||||
lastReadMessage: state.messages[
|
||||
navigator.product === 'ReactNative' ? 0 : state.messages.length - 1],
|
||||
privateMessageRecipient: state.isOpen ? undefined : state.privateMessageRecipient
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,17 +8,11 @@ import { getParticipantCount } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { beginAddPeople } from '../../../invite';
|
||||
import { isButtonEnabled, isToolboxVisible } from '../../../toolbox/functions.web';
|
||||
import { shouldDisplayTileView } from '../../../video-layout/functions';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Whether tile view is enabled.
|
||||
*/
|
||||
_tileViewEnabled: Boolean,
|
||||
|
||||
/**
|
||||
* Whether to show the option to invite more people
|
||||
* instead of the subject.
|
||||
@@ -44,14 +38,13 @@ type Props = {
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
function InviteMore({
|
||||
_tileViewEnabled,
|
||||
_visible,
|
||||
onClick,
|
||||
t
|
||||
}: Props) {
|
||||
return (
|
||||
_visible
|
||||
? <div className = { `invite-more-container${_tileViewEnabled ? ' elevated' : ''}` }>
|
||||
? <div className = 'invite-more-container'>
|
||||
<div className = 'invite-more-header'>
|
||||
{t('addPeople.inviteMoreHeader')}
|
||||
</div>
|
||||
@@ -81,7 +74,6 @@ function mapStateToProps(state) {
|
||||
const hide = interfaceConfig.HIDE_INVITE_MORE_HEADER;
|
||||
|
||||
return {
|
||||
_tileViewEnabled: shouldDisplayTileView(state),
|
||||
_visible: isToolboxVisible(state) && isButtonEnabled('invite') && isAlone && !hide
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,12 +7,18 @@ import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { CustomSubmitDialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
import { toggleLobbyMode } from '../../actions';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_dialogStyles: StyleType,
|
||||
|
||||
/**
|
||||
* The Redux Dispatch function.
|
||||
*/
|
||||
@@ -51,7 +57,7 @@ class EnableLobbyModeDialog extends PureComponent<Props> {
|
||||
onSubmit = { this._onEnableLobbyMode }
|
||||
titleKey = 'lobby.dialogTitle'>
|
||||
<View style = { styles.formWrapper }>
|
||||
<Text>
|
||||
<Text style = { this.props._dialogStyles.text } >
|
||||
{ this.props.t('lobby.enableDialogText') }
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -27,9 +27,12 @@ import {
|
||||
} from '../../base/connection';
|
||||
import { JitsiConferenceEvents } from '../../base/lib-jitsi-meet';
|
||||
import { SET_AUDIO_MUTED } from '../../base/media/actionTypes';
|
||||
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT, getParticipants } from '../../base/participants';
|
||||
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT, getParticipants, getParticipantById } from '../../base/participants';
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../../base/redux';
|
||||
import { toggleScreensharing } from '../../base/tracks';
|
||||
import { OPEN_CHAT, CLOSE_CHAT } from '../../chat';
|
||||
import { openChat } from '../../chat/actions';
|
||||
import { sendMessage, setPrivateMessageRecipient, closeChat } from '../../chat/actions.any';
|
||||
import { muteLocal } from '../../remote-video-menu/actions';
|
||||
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture';
|
||||
|
||||
@@ -37,6 +40,17 @@ import { setParticipantsWithScreenShare } from './actions';
|
||||
import { sendEvent } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Event which will be emitted on the native side when a chat message is received
|
||||
* through the channel.
|
||||
*/
|
||||
const CHAT_MESSAGE_RECEIVED = 'CHAT_MESSAGE_RECEIVED';
|
||||
|
||||
/**
|
||||
* Event which will be emitted on the native side when the chat dialog is displayed/closed.
|
||||
*/
|
||||
const CHAT_TOGGLED = 'CHAT_TOGGLED';
|
||||
|
||||
/**
|
||||
* Event which will be emitted on the native side to indicate the conference
|
||||
* has ended either by user request or because an error was produced.
|
||||
@@ -152,6 +166,17 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
case OPEN_CHAT:
|
||||
case CLOSE_CHAT: {
|
||||
sendEvent(
|
||||
store,
|
||||
CHAT_TOGGLED,
|
||||
/* data */ {
|
||||
isOpen: action.type === OPEN_CHAT
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case PARTICIPANT_JOINED:
|
||||
case PARTICIPANT_LEFT: {
|
||||
const { participant } = action;
|
||||
@@ -237,7 +262,9 @@ StateListenerRegistry.register(
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _registerForNativeEvents({ getState, dispatch }) {
|
||||
function _registerForNativeEvents(store) {
|
||||
const { getState, dispatch } = store;
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.HANG_UP, () => {
|
||||
dispatch(appNavigate(undefined));
|
||||
});
|
||||
@@ -264,7 +291,6 @@ function _registerForNativeEvents({ getState, dispatch }) {
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.RETRIEVE_PARTICIPANTS_INFO, ({ requestId }) => {
|
||||
const store = getState();
|
||||
|
||||
const participantsInfo = getParticipants(store).map(participant => {
|
||||
return {
|
||||
@@ -286,6 +312,27 @@ function _registerForNativeEvents({ getState, dispatch }) {
|
||||
requestId
|
||||
});
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.OPEN_CHAT, ({ to }) => {
|
||||
const participant = getParticipantById(store, to);
|
||||
|
||||
dispatch(openChat(participant));
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.CLOSE_CHAT, () => {
|
||||
dispatch(closeChat());
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.SEND_CHAT_MESSAGE, ({ message, to }) => {
|
||||
const participant = getParticipantById(store, to);
|
||||
|
||||
if (participant) {
|
||||
dispatch(setPrivateMessageRecipient(participant));
|
||||
}
|
||||
|
||||
dispatch(sendMessage(message));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -315,6 +362,36 @@ function _registerForEndpointTextMessages(store) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.MESSAGE_RECEIVED,
|
||||
(id, message, timestamp) => {
|
||||
sendEvent(
|
||||
store,
|
||||
CHAT_MESSAGE_RECEIVED,
|
||||
/* data */ {
|
||||
senderId: id,
|
||||
message,
|
||||
isPrivate: false,
|
||||
timestamp
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.PRIVATE_MESSAGE_RECEIVED,
|
||||
(id, message, timestamp) => {
|
||||
sendEvent(
|
||||
store,
|
||||
CHAT_MESSAGE_RECEIVED,
|
||||
/* data */ {
|
||||
senderId: id,
|
||||
message,
|
||||
isPrivate: true,
|
||||
timestamp
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,7 +46,9 @@ MiddlewareRegistry.register(store => next => async action => {
|
||||
await dispatch(replaceLocalTrack(localVideoTrack.jitsiTrack, null));
|
||||
}
|
||||
|
||||
const jitsiTracks = getState()['features/base/tracks'].map(t => t.jitsiTrack);
|
||||
const jitsiTracks = getState()['features/base/tracks']
|
||||
.map(t => t.jitsiTrack)
|
||||
.filter(t => Boolean(t)); // Filter out GUM in progress tracks...
|
||||
|
||||
dispatch(setPrejoinPageVisibility(false));
|
||||
APP.conference.prejoinStart(jitsiTracks);
|
||||
|
||||
@@ -5,8 +5,8 @@ import React, { Component } from 'react';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconMessage } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { openChat } from '../../../chat/';
|
||||
import {
|
||||
_mapDispatchToProps,
|
||||
_mapStateToProps as _abstractMapStateToProps,
|
||||
type Props as AbstractProps
|
||||
} from '../../../chat/components/PrivateMessageButton';
|
||||
@@ -72,9 +72,9 @@ class PrivateMessageMenuButton extends Component<Props> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick() {
|
||||
const { _participant, _setPrivateMessageRecipient } = this.props;
|
||||
const { dispatch, _participant } = this.props;
|
||||
|
||||
_setPrivateMessageRecipient(_participant);
|
||||
dispatch(openChat(_participant));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,4 +93,4 @@ function _mapStateToProps(state: Object, ownProps: Props): $Shape<Props> {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(PrivateMessageMenuButton));
|
||||
export default translate(connect(_mapStateToProps)(PrivateMessageMenuButton));
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
// @flow
|
||||
|
||||
import * as StackBlur from 'stackblur-canvas';
|
||||
|
||||
import {
|
||||
CLEAR_TIMEOUT,
|
||||
TIMEOUT_TICK,
|
||||
@@ -9,21 +6,27 @@ import {
|
||||
timerWorkerScript
|
||||
} from './TimerWorker';
|
||||
|
||||
const segmentationWidth = 256;
|
||||
const segmentationHeight = 144;
|
||||
const segmentationPixelCount = segmentationWidth * segmentationHeight;
|
||||
const blurValue = '25px';
|
||||
|
||||
/**
|
||||
* Represents a modified MediaStream that adds blur to video background.
|
||||
* <tt>JitsiStreamBlurEffect</tt> does the processing of the original
|
||||
* video stream.
|
||||
*/
|
||||
export default class JitsiStreamBlurEffect {
|
||||
_bpModel: Object;
|
||||
_model: Object;
|
||||
_inputVideoElement: HTMLVideoElement;
|
||||
_inputVideoCanvasElement: HTMLCanvasElement;
|
||||
_onMaskFrameTimer: Function;
|
||||
_maskFrameTimerWorker: Worker;
|
||||
_maskInProgress: boolean;
|
||||
_outputCanvasElement: HTMLCanvasElement;
|
||||
_outputCanvasCtx: Object;
|
||||
_segmentationMaskCtx: Object;
|
||||
_segmentationMask: Object;
|
||||
_segmentationMaskCanvas: Object;
|
||||
_renderMask: Function;
|
||||
_segmentationData: Object;
|
||||
isEnabled: Function;
|
||||
startEffect: Function;
|
||||
stopEffect: Function;
|
||||
@@ -35,7 +38,7 @@ export default class JitsiStreamBlurEffect {
|
||||
* @param {BodyPix} bpModel - BodyPix model.
|
||||
*/
|
||||
constructor(bpModel: Object) {
|
||||
this._bpModel = bpModel;
|
||||
this._model = bpModel;
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onMaskFrameTimer = this._onMaskFrameTimer.bind(this);
|
||||
@@ -44,7 +47,6 @@ export default class JitsiStreamBlurEffect {
|
||||
this._outputCanvasElement = document.createElement('canvas');
|
||||
this._outputCanvasElement.getContext('2d');
|
||||
this._inputVideoElement = document.createElement('video');
|
||||
this._inputVideoCanvasElement = document.createElement('canvas');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,62 +62,109 @@ export default class JitsiStreamBlurEffect {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the run post processing.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
runPostProcessing() {
|
||||
this._outputCanvasCtx.globalCompositeOperation = 'copy';
|
||||
|
||||
// Draw segmentation mask.
|
||||
this._outputCanvasCtx.filter = `blur(${blurValue})`;
|
||||
this._outputCanvasCtx.drawImage(
|
||||
this._segmentationMaskCanvas,
|
||||
0,
|
||||
0,
|
||||
segmentationWidth,
|
||||
segmentationHeight,
|
||||
0,
|
||||
0,
|
||||
this._inputVideoElement.width,
|
||||
this._inputVideoElement.height
|
||||
);
|
||||
|
||||
this._outputCanvasCtx.globalCompositeOperation = 'source-in';
|
||||
this._outputCanvasCtx.filter = 'none';
|
||||
this._outputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
|
||||
|
||||
this._outputCanvasCtx.globalCompositeOperation = 'destination-over';
|
||||
this._outputCanvasCtx.filter = `blur(${blurValue})`; // FIXME Does not work on Safari.
|
||||
this._outputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the run Tensorflow Interference.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
runInference() {
|
||||
this._model._runInference();
|
||||
const outputMemoryOffset = this._model._getOutputMemoryOffset() / 4;
|
||||
|
||||
for (let i = 0; i < segmentationPixelCount; i++) {
|
||||
const background = this._model.HEAPF32[outputMemoryOffset + (i * 2)];
|
||||
const person = this._model.HEAPF32[outputMemoryOffset + (i * 2) + 1];
|
||||
const shift = Math.max(background, person);
|
||||
const backgroundExp = Math.exp(background - shift);
|
||||
const personExp = Math.exp(person - shift);
|
||||
|
||||
// Sets only the alpha component of each pixel.
|
||||
this._segmentationMask.data[(i * 4) + 3] = (255 * personExp) / (backgroundExp + personExp);
|
||||
}
|
||||
this._segmentationMaskCtx.putImageData(this._segmentationMask, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop function to render the background mask.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
async _renderMask() {
|
||||
if (!this._maskInProgress) {
|
||||
this._maskInProgress = true;
|
||||
this._bpModel.segmentPerson(this._inputVideoElement, {
|
||||
internalResolution: 'low', // resized to 0.5 times of the original resolution before inference
|
||||
maxDetections: 1, // max. number of person poses to detect per image
|
||||
segmentationThreshold: 0.7, // represents probability that a pixel belongs to a person
|
||||
flipHorizontal: false,
|
||||
scoreThreshold: 0.2
|
||||
}).then(data => {
|
||||
this._segmentationData = data;
|
||||
this._maskInProgress = false;
|
||||
});
|
||||
}
|
||||
const inputCanvasCtx = this._inputVideoCanvasElement.getContext('2d');
|
||||
_renderMask() {
|
||||
this.resizeSource();
|
||||
this.runInference();
|
||||
this.runPostProcessing();
|
||||
|
||||
inputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
|
||||
|
||||
const currentFrame = inputCanvasCtx.getImageData(
|
||||
0,
|
||||
0,
|
||||
this._inputVideoCanvasElement.width,
|
||||
this._inputVideoCanvasElement.height
|
||||
);
|
||||
|
||||
if (this._segmentationData) {
|
||||
const blurData = new ImageData(currentFrame.data.slice(), currentFrame.width, currentFrame.height);
|
||||
|
||||
StackBlur.imageDataRGB(blurData, 0, 0, currentFrame.width, currentFrame.height, 12);
|
||||
|
||||
for (let x = 0; x < this._outputCanvasElement.width; x++) {
|
||||
for (let y = 0; y < this._outputCanvasElement.height; y++) {
|
||||
const n = (y * this._outputCanvasElement.width) + x;
|
||||
|
||||
if (this._segmentationData.data[n] === 0) {
|
||||
currentFrame.data[n * 4] = blurData.data[n * 4];
|
||||
currentFrame.data[(n * 4) + 1] = blurData.data[(n * 4) + 1];
|
||||
currentFrame.data[(n * 4) + 2] = blurData.data[(n * 4) + 2];
|
||||
currentFrame.data[(n * 4) + 3] = blurData.data[(n * 4) + 3];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this._outputCanvasElement.getContext('2d').putImageData(currentFrame, 0, 0);
|
||||
this._maskFrameTimerWorker.postMessage({
|
||||
id: SET_TIMEOUT,
|
||||
timeMs: 1000 / 30
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the resize source process.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
resizeSource() {
|
||||
this._segmentationMaskCtx.drawImage(
|
||||
this._inputVideoElement,
|
||||
0,
|
||||
0,
|
||||
this._inputVideoElement.width,
|
||||
this._inputVideoElement.height,
|
||||
0,
|
||||
0,
|
||||
segmentationWidth,
|
||||
segmentationHeight
|
||||
);
|
||||
|
||||
const imageData = this._segmentationMaskCtx.getImageData(
|
||||
0,
|
||||
0,
|
||||
segmentationWidth,
|
||||
segmentationHeight
|
||||
);
|
||||
const inputMemoryOffset = this._model._getInputMemoryOffset() / 4;
|
||||
|
||||
for (let i = 0; i < segmentationPixelCount; i++) {
|
||||
this._model.HEAPF32[inputMemoryOffset + (i * 3)] = imageData.data[i * 4] / 255;
|
||||
this._model.HEAPF32[inputMemoryOffset + (i * 3) + 1] = imageData.data[(i * 4) + 1] / 255;
|
||||
this._model.HEAPF32[inputMemoryOffset + (i * 3) + 2] = imageData.data[(i * 4) + 2] / 255;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the local track supports this effect.
|
||||
*
|
||||
@@ -136,15 +185,18 @@ export default class JitsiStreamBlurEffect {
|
||||
startEffect(stream: MediaStream) {
|
||||
this._maskFrameTimerWorker = new Worker(timerWorkerScript, { name: 'Blur effect worker' });
|
||||
this._maskFrameTimerWorker.onmessage = this._onMaskFrameTimer;
|
||||
|
||||
const firstVideoTrack = stream.getVideoTracks()[0];
|
||||
const { height, frameRate, width }
|
||||
= firstVideoTrack.getSettings ? firstVideoTrack.getSettings() : firstVideoTrack.getConstraints();
|
||||
|
||||
this._segmentationMask = new ImageData(segmentationWidth, segmentationHeight);
|
||||
this._segmentationMaskCanvas = document.createElement('canvas');
|
||||
this._segmentationMaskCanvas.width = segmentationWidth;
|
||||
this._segmentationMaskCanvas.height = segmentationHeight;
|
||||
this._segmentationMaskCtx = this._segmentationMaskCanvas.getContext('2d');
|
||||
this._outputCanvasElement.width = parseInt(width, 10);
|
||||
this._outputCanvasElement.height = parseInt(height, 10);
|
||||
this._inputVideoCanvasElement.width = parseInt(width, 10);
|
||||
this._inputVideoCanvasElement.height = parseInt(height, 10);
|
||||
this._outputCanvasCtx = this._outputCanvasElement.getContext('2d');
|
||||
this._inputVideoElement.width = parseInt(width, 10);
|
||||
this._inputVideoElement.height = parseInt(height, 10);
|
||||
this._inputVideoElement.autoplay = true;
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
// @flow
|
||||
|
||||
import * as bodyPix from '@tensorflow-models/body-pix';
|
||||
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 = {
|
||||
'96': 'libs/segm_lite_v681.tflite',
|
||||
'144': 'libs/segm_full_v679.tflite'
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new instance of JitsiStreamBlurEffect. This loads the bodyPix model that is used to
|
||||
@@ -14,15 +21,24 @@ export async function createBlurEffect() {
|
||||
if (!MediaStreamTrack.prototype.getSettings && !MediaStreamTrack.prototype.getConstraints) {
|
||||
throw new Error('JitsiStreamBlurEffect not supported!');
|
||||
}
|
||||
let tflite;
|
||||
|
||||
// An output stride of 16 and a multiplier of 0.5 are used for improved
|
||||
// performance on a larger range of CPUs.
|
||||
const bpModel = await bodyPix.load({
|
||||
architecture: 'MobileNetV1',
|
||||
outputStride: 16,
|
||||
multiplier: 0.50,
|
||||
quantBytes: 2
|
||||
});
|
||||
if (wasmCheck.feature.simd) {
|
||||
tflite = await createTFLiteSIMDModule();
|
||||
} else {
|
||||
tflite = await createTFLiteModule();
|
||||
}
|
||||
|
||||
return new JitsiStreamBlurEffect(bpModel);
|
||||
const modelBufferOffset = tflite._getModelBufferMemoryOffset();
|
||||
const modelResponse = await fetch(
|
||||
models['144']
|
||||
);
|
||||
|
||||
const model = await modelResponse.arrayBuffer();
|
||||
|
||||
tflite.HEAPU8.set(new Uint8Array(model), modelBufferOffset);
|
||||
|
||||
tflite._loadModel(model.byteLength);
|
||||
|
||||
return new JitsiStreamBlurEffect(tflite);
|
||||
}
|
||||
|
||||
24
react/features/stream-effects/blur/vendor/README.md
vendored
Normal file
24
react/features/stream-effects/blur/vendor/README.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Virtual Background on stream effects
|
||||
|
||||
> Inspired from https://ai.googleblog.com/2020/10/background-features-in-google-meet.html and https://github.com/Volcomix/virtual-background.git
|
||||
|
||||
#### Canvas 2D + CPU
|
||||
|
||||
This rendering pipeline is pretty much the same as for BodyPix. It relies on Canvas compositing properties to blend rendering layers according to the segmentation mask.
|
||||
|
||||
Interactions with TFLite inference tool are executed on CPU to convert from UInt8 to Float32 for the model input and to apply softmax on the model output.
|
||||
|
||||
The framerate is higher and the quality looks better than BodyPix
|
||||
|
||||
#### SIMD and non-SIMD
|
||||
|
||||
How to test on SIMD:
|
||||
1. Go to chrome://flags/
|
||||
2. Search for SIMD flag
|
||||
3. Enable WebAssembly SIMD support(Enables support for the WebAssembly SIMD proposal).
|
||||
4. Reopen Google Chrome
|
||||
|
||||
More details:
|
||||
- [WebAssembly](https://webassembly.org/)
|
||||
- [WebAssembly SIMD](https://github.com/WebAssembly/simd)
|
||||
- [TFLite](https://blog.tensorflow.org/2020/07/accelerating-tensorflow-lite-xnnpack-integration.html)
|
||||
BIN
react/features/stream-effects/blur/vendor/models/segm_full_v679.tflite
vendored
Normal file
BIN
react/features/stream-effects/blur/vendor/models/segm_full_v679.tflite
vendored
Normal file
Binary file not shown.
BIN
react/features/stream-effects/blur/vendor/models/segm_lite_v681.tflite
vendored
Normal file
BIN
react/features/stream-effects/blur/vendor/models/segm_lite_v681.tflite
vendored
Normal file
Binary file not shown.
21
react/features/stream-effects/blur/vendor/tflite/tflite-simd.js
vendored
Normal file
21
react/features/stream-effects/blur/vendor/tflite/tflite-simd.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
react/features/stream-effects/blur/vendor/tflite/tflite-simd.wasm
vendored
Executable file
BIN
react/features/stream-effects/blur/vendor/tflite/tflite-simd.wasm
vendored
Executable file
Binary file not shown.
21
react/features/stream-effects/blur/vendor/tflite/tflite.js
vendored
Normal file
21
react/features/stream-effects/blur/vendor/tflite/tflite.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
react/features/stream-effects/blur/vendor/tflite/tflite.wasm
vendored
Executable file
BIN
react/features/stream-effects/blur/vendor/tflite/tflite.wasm
vendored
Executable file
Binary file not shown.
@@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics';
|
||||
import { getFeatureFlag, HELP_BUTTON_ENABLED } from '../../base/flags';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { IconHelp } from '../../base/icons';
|
||||
import { connect } from '../../base/redux';
|
||||
@@ -45,7 +46,8 @@ class HelpButton extends AbstractButton<Props, *> {
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
const { userDocumentationURL } = state['features/base/config'].deploymentUrls || {};
|
||||
const visible = typeof userDocumentationURL === 'string';
|
||||
const enabled = getFeatureFlag(state, HELP_BUTTON_ENABLED, true);
|
||||
const visible = typeof userDocumentationURL === 'string' && enabled;
|
||||
|
||||
return {
|
||||
_userDocumentationURL: userDocumentationURL,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import * as wasmCheck from 'wasm-check';
|
||||
|
||||
import {
|
||||
ACTION_SHORTCUT_TRIGGERED,
|
||||
@@ -1069,7 +1070,7 @@ class Toolbox extends Component<Props, State> {
|
||||
&& <VideoBlurButton
|
||||
key = 'videobackgroundblur'
|
||||
showLabel = { true }
|
||||
visible = { !_screensharing } />,
|
||||
visible = { !_screensharing && wasmCheck.feature.simd } />,
|
||||
this._shouldShowButton('settings')
|
||||
&& <SettingsButton
|
||||
key = 'settings'
|
||||
|
||||
@@ -13,15 +13,16 @@ const {
|
||||
localVideo = config.startWithVideoMuted !== true,
|
||||
remoteVideo = isHuman,
|
||||
remoteAudio = isHuman,
|
||||
autoPlayVideo = config.testing.noAutoPlayVideo !== true
|
||||
autoPlayVideo = config.testing.noAutoPlayVideo !== true,
|
||||
|
||||
// Whether to create local audio even if muted
|
||||
autoCreateLocalAudio = config.testing.noAutoLocalAudio !== true
|
||||
} = params;
|
||||
|
||||
const { room: roomName } = parseURIString(window.location.toString());
|
||||
|
||||
let connection = null;
|
||||
|
||||
let isJoined = false;
|
||||
|
||||
let room = null;
|
||||
|
||||
let numParticipants = 1;
|
||||
@@ -107,13 +108,15 @@ function onLocalTracks(tracks = []) {
|
||||
$('body').append(`<video ${autoPlayVideo ? 'autoplay="1" ' : ''}id='localVideo${i}' />`);
|
||||
localTracks[i].attach($(`#localVideo${i}`)[0]);
|
||||
} else {
|
||||
if (!localAudio) {
|
||||
localTracks[i].mute();
|
||||
}
|
||||
|
||||
$('body').append(
|
||||
`<audio autoplay='1' muted='true' id='localAudio${i}' />`);
|
||||
localTracks[i].attach($(`#localAudio${i}`)[0]);
|
||||
}
|
||||
if (isJoined) {
|
||||
room.addTrack(localTracks[i]);
|
||||
}
|
||||
room.addTrack(localTracks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,10 +149,28 @@ function onRemoteTrack(track) {
|
||||
* That function is executed when the conference is joined
|
||||
*/
|
||||
function onConferenceJoined() {
|
||||
isJoined = true;
|
||||
for (let i = 0; i < localTracks.length; i++) {
|
||||
room.addTrack(localTracks[i]);
|
||||
}
|
||||
console.log('Conference joined');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles start muted events, when audio and/or video are muted due to
|
||||
* startAudioMuted or startVideoMuted policy.
|
||||
*/
|
||||
function onStartMuted() {
|
||||
// Give it some time, as it may be currently in the process of muting
|
||||
setTimeout(() => {
|
||||
const localAudioTrack = room.getLocalAudioTrack();
|
||||
|
||||
if (localAudio && localAudioTrack && localAudioTrack.isMuted()) {
|
||||
localAudioTrack.unmute();
|
||||
}
|
||||
|
||||
const localVideoTrack = room.getLocalVideoTrack();
|
||||
|
||||
if (localVideo && localVideoTrack && localVideoTrack.isMuted()) {
|
||||
localVideoTrack.unmute();
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,6 +200,7 @@ function onUserLeft(id) {
|
||||
*/
|
||||
function onConnectionSuccess() {
|
||||
room = connection.initJitsiConference(roomName.toLowerCase(), config);
|
||||
room.on(JitsiMeetJS.events.conference.STARTED_MUTED, onStartMuted);
|
||||
room.on(JitsiMeetJS.events.conference.TRACK_ADDED, onRemoteTrack);
|
||||
room.on(JitsiMeetJS.events.conference.CONFERENCE_JOINED, onConferenceJoined);
|
||||
room.on(JitsiMeetJS.events.conference.USER_JOINED, id => {
|
||||
@@ -187,7 +209,30 @@ function onConnectionSuccess() {
|
||||
remoteTracks[id] = [];
|
||||
});
|
||||
room.on(JitsiMeetJS.events.conference.USER_LEFT, onUserLeft);
|
||||
room.join();
|
||||
|
||||
const devices = [];
|
||||
|
||||
if (localVideo) {
|
||||
devices.push('video');
|
||||
}
|
||||
|
||||
if (autoCreateLocalAudio) {
|
||||
devices.push('audio');
|
||||
}
|
||||
|
||||
if (devices.length > 0) {
|
||||
JitsiMeetJS.createLocalTracks({ devices })
|
||||
.then(onLocalTracks)
|
||||
.then(() => {
|
||||
room.join();
|
||||
})
|
||||
.catch(error => {
|
||||
throw error;
|
||||
});
|
||||
} else {
|
||||
room.join();
|
||||
}
|
||||
|
||||
updateMaxFrameHeight();
|
||||
}
|
||||
|
||||
@@ -233,27 +278,12 @@ JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
|
||||
JitsiMeetJS.init(config);
|
||||
|
||||
config.serviceUrl = config.bosh = `${config.websocket || config.bosh}?room=${roomName.toLowerCase()}`;
|
||||
if (config.websocketKeepAliveUrl) {
|
||||
config.websocketKeepAliveUrl += `?room=${roomName.toLowerCase()}`;
|
||||
}
|
||||
|
||||
connection = new JitsiMeetJS.JitsiConnection(null, null, config);
|
||||
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED, onConnectionSuccess);
|
||||
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, onConnectionFailed);
|
||||
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, disconnect);
|
||||
connection.connect();
|
||||
|
||||
const devices = [];
|
||||
|
||||
if (localVideo) {
|
||||
devices.push('video');
|
||||
}
|
||||
if (localAudio) {
|
||||
devices.push('audio');
|
||||
}
|
||||
if (devices.length > 0) {
|
||||
JitsiMeetJS.createLocalTracks({ devices })
|
||||
.then(onLocalTracks)
|
||||
.catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user