mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-09-10 08:38:40 +00:00
Compare commits
1 Commits
4740
...
saghul-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f4c036174 |
@@ -5,7 +5,7 @@ build/*
|
||||
# modify as little as possible.
|
||||
flow-typed/*
|
||||
libs/*
|
||||
resources/*
|
||||
|
||||
react/features/stream-effects/blur/vendor/*
|
||||
|
||||
# ESLint will by default ignore its own configuration file. However, there does
|
||||
|
||||
@@ -115,7 +115,7 @@ import {
|
||||
submitFeedback
|
||||
} from './react/features/feedback';
|
||||
import { showNotification } from './react/features/notifications';
|
||||
import { mediaPermissionPromptVisibilityChanged, toggleSlowGUMOverlay } from './react/features/overlay';
|
||||
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay';
|
||||
import { suspendDetected } from './react/features/power-monitor';
|
||||
import {
|
||||
initPrejoin,
|
||||
@@ -502,11 +502,6 @@ export default {
|
||||
);
|
||||
}
|
||||
|
||||
JitsiMeetJS.mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.SLOW_GET_USER_MEDIA,
|
||||
() => APP.store.dispatch(toggleSlowGUMOverlay(true))
|
||||
);
|
||||
|
||||
let tryCreateLocalTracks;
|
||||
|
||||
// On Electron there is no permission prompt for granting permissions. That's why we don't need to
|
||||
@@ -524,10 +519,8 @@ export default {
|
||||
|
||||
return createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
fireSlowPromiseEvent: true
|
||||
})
|
||||
timeout
|
||||
}, true)
|
||||
.then(([ audioStream ]) =>
|
||||
[ desktopStream, audioStream ])
|
||||
.catch(error => {
|
||||
@@ -543,10 +536,8 @@ export default {
|
||||
return requestedAudio
|
||||
? createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
fireSlowPromiseEvent: true
|
||||
})
|
||||
timeout
|
||||
}, true)
|
||||
: [];
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -560,10 +551,8 @@ export default {
|
||||
} else {
|
||||
tryCreateLocalTracks = createLocalTracksF({
|
||||
devices: initialDevices,
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
fireSlowPromiseEvent: true
|
||||
})
|
||||
timeout
|
||||
}, true)
|
||||
.catch(err => {
|
||||
if (requestedAudio && requestedVideo) {
|
||||
|
||||
@@ -585,10 +574,8 @@ export default {
|
||||
return (
|
||||
createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
fireSlowPromiseEvent: true
|
||||
}));
|
||||
timeout
|
||||
}, true));
|
||||
} else if (requestedAudio && !requestedVideo) {
|
||||
errors.audioOnlyError = err;
|
||||
|
||||
@@ -611,9 +598,8 @@ export default {
|
||||
return requestedVideo
|
||||
? createLocalTracksF({
|
||||
devices: [ 'video' ],
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
fireSlowPromiseEvent: true
|
||||
})
|
||||
timeout
|
||||
}, true)
|
||||
: [];
|
||||
})
|
||||
.catch(err => {
|
||||
@@ -633,7 +619,6 @@ export default {
|
||||
// the user inputs their credentials, but the dialog would be
|
||||
// overshadowed by the overlay.
|
||||
tryCreateLocalTracks.then(tracks => {
|
||||
APP.store.dispatch(toggleSlowGUMOverlay(false));
|
||||
APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
|
||||
|
||||
return tracks;
|
||||
@@ -897,7 +882,7 @@ export default {
|
||||
showUI && APP.store.dispatch(notifyMicError(error));
|
||||
};
|
||||
|
||||
createLocalTracksF({ devices: [ 'audio' ] })
|
||||
createLocalTracksF({ devices: [ 'audio' ] }, false)
|
||||
.then(([ audioTrack ]) => audioTrack)
|
||||
.catch(error => {
|
||||
maybeShowErrorDialog(error);
|
||||
@@ -1011,7 +996,7 @@ export default {
|
||||
//
|
||||
// FIXME when local track creation is moved to react/redux
|
||||
// it should take care of the use case described above
|
||||
createLocalTracksF({ devices: [ 'video' ] })
|
||||
createLocalTracksF({ devices: [ 'video' ] }, false)
|
||||
.then(([ videoTrack ]) => videoTrack)
|
||||
.catch(error => {
|
||||
// FIXME should send some feedback to the API on error ?
|
||||
@@ -2008,10 +1993,7 @@ export default {
|
||||
|
||||
room.on(JitsiConferenceEvents.TRACK_MUTE_CHANGED, (track, participantThatMutedUs) => {
|
||||
if (participantThatMutedUs) {
|
||||
APP.store.dispatch(participantMutedUs(participantThatMutedUs, track));
|
||||
if (this.isSharingScreen && track.isVideoTrack()) {
|
||||
this._turnScreenSharingOff(false);
|
||||
}
|
||||
APP.store.dispatch(participantMutedUs(participantThatMutedUs));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -240,12 +240,6 @@ var config = {
|
||||
// 90: 2
|
||||
// },
|
||||
|
||||
// Provides a way to translate the legacy bridge signaling messages, 'LastNChangedEvent',
|
||||
// 'SelectedEndpointsChangedEvent' and 'ReceiverVideoConstraint' into the new 'ReceiverVideoConstraints' message
|
||||
// that invokes the new bandwidth allocation algorithm in the bridge which is described here
|
||||
// - https://github.com/jitsi/jitsi-videobridge/blob/master/doc/allocation.md.
|
||||
// useNewBandwidthAllocationStrategy: false,
|
||||
|
||||
// Specify the settings for video quality optimizations on the client.
|
||||
// videoQuality: {
|
||||
// // Provides a way to prevent a video codec from being negotiated on the JVB connection. The codec specified
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
/**
|
||||
* Mixins that mimic the way Atlaskit fills the screen with modals at low screen widths.
|
||||
*/
|
||||
@mixin full-size-modal-positioner() {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@mixin full-size-modal-dialog() {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the @atlaskit/flag container up a little bit so it does not cover the
|
||||
* toolbar with the first notification.
|
||||
@@ -74,43 +56,4 @@
|
||||
.toolbox-button-wth-dialog > div:nth-child(2) {
|
||||
max-height: calc(100vh - #{$newToolbarSizeWithPadding} - 46px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following selectors keep the chat modal full-size anywhere between 100px
|
||||
* and 580px for desktop or 680px for mobile.
|
||||
*/
|
||||
@media (min-width: 100px) and (max-width: 320px) {
|
||||
.smiley-input {
|
||||
display: none;
|
||||
}
|
||||
.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 480px) and (max-width: 580px) {
|
||||
.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 580px) and (max-width: 680px) {
|
||||
.mobile-browser {
|
||||
&.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
&.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
109
css/_chat.scss
109
css/_chat.scss
@@ -32,13 +32,6 @@
|
||||
width: $sidebarWidth;
|
||||
word-wrap: break-word;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > :first-child {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
@@ -129,61 +122,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chat-input-container {
|
||||
padding: 0 16px 24px;
|
||||
|
||||
&.populated {
|
||||
#chat-input {
|
||||
border: 1px solid #619CF4;
|
||||
|
||||
.send-button {
|
||||
background: #1B67EC;
|
||||
cursor: pointer;
|
||||
|
||||
path {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#chat-input {
|
||||
border: 1px solid $chatInputSeparatorColor;
|
||||
border-top: 1px solid $chatInputSeparatorColor;
|
||||
display: flex;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
|
||||
* {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.send-button-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.send-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 3px;
|
||||
|
||||
path {
|
||||
fill: $chatInputSeparatorColor;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-browser {
|
||||
.send-button {
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.remoteuser {
|
||||
color: #B8C7E0;
|
||||
}
|
||||
@@ -213,47 +161,10 @@
|
||||
#nickname {
|
||||
text-align: center;
|
||||
color: #9d9d9d;
|
||||
font-size: 16px;
|
||||
margin: auto 0;
|
||||
padding: 0 16px;
|
||||
|
||||
input {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
label {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.enter-chat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
height: 40px;
|
||||
background: #1B67EC;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
|
||||
&.disabled {
|
||||
color: #757575;
|
||||
background: #11336E;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-browser {
|
||||
#nickname {
|
||||
input {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.enter-chat {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
font-size: 18px;
|
||||
margin-top: 30px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.sideToolbarContainer {
|
||||
@@ -500,16 +411,6 @@
|
||||
#chatconversation {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chat-input-container {
|
||||
padding: 0 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.touchmove-hack {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
min-width: 75px;
|
||||
text-align: left;
|
||||
padding: 0px;
|
||||
width: 180px;
|
||||
white-space: nowrap;
|
||||
|
||||
&__item {
|
||||
|
||||
@@ -53,6 +53,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
@mixin full-size-modal-positioner() {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@mixin full-size-modal-dialog() {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $verySmallScreen) {
|
||||
.welcome {
|
||||
display: block;
|
||||
@@ -150,3 +165,25 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 480px) and (max-width: 580px) {
|
||||
.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 580px) and (max-width: 680px) {
|
||||
.mobile-browser {
|
||||
&.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
&.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,12 +33,4 @@
|
||||
bottom: 24px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__spinner-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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', 'videobackgroundblur', 'download', 'help', 'mute-everyone', 'security'
|
||||
],
|
||||
|
||||
TOOLBAR_TIMEOUT: 4000,
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
|
||||
- (void)sendHangUp;
|
||||
- (void)sendSetAudioMuted:(BOOL)muted;
|
||||
- (void)sendEndpointTextMessage:(NSString*)message :(NSString*)to;
|
||||
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message;
|
||||
- (void)toggleScreenShare;
|
||||
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completion;
|
||||
- (void)openChat:(NSString*)to;
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString*)message :(NSString*)to ;
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message;
|
||||
|
||||
@end
|
||||
|
||||
@@ -153,7 +153,7 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
[self sendEventWithName:setAudioMutedAction body:data];
|
||||
}
|
||||
|
||||
- (void)sendEndpointTextMessage:(NSString*)message :(NSString*)to {
|
||||
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message {
|
||||
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
|
||||
data[@"to"] = to;
|
||||
data[@"message"] = message;
|
||||
@@ -185,7 +185,7 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
[self sendEventWithName:closeChatAction body:nil];
|
||||
}
|
||||
|
||||
- (void)sendChatMessage:(NSString*)message :(NSString*)to {
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message {
|
||||
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
|
||||
data[@"to"] = to;
|
||||
data[@"message"] = message;
|
||||
|
||||
@@ -38,11 +38,11 @@
|
||||
- (void)leave;
|
||||
- (void)hangUp;
|
||||
- (void)setAudioMuted:(BOOL)muted;
|
||||
- (void)sendEndpointTextMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
|
||||
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message;
|
||||
- (void)toggleScreenShare;
|
||||
- (void)retrieveParticipantsInfo:(void (^ _Nonnull)(NSArray * _Nullable))completionHandler;
|
||||
- (void)openChat:(NSString * _Nullable)to;
|
||||
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completionHandler;
|
||||
- (void)openChat:(NSString*)to;
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message;
|
||||
|
||||
@end
|
||||
|
||||
@@ -125,9 +125,9 @@ static void initializeViewsMap() {
|
||||
[externalAPI sendSetAudioMuted:muted];
|
||||
}
|
||||
|
||||
- (void)sendEndpointTextMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
|
||||
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendEndpointTextMessage:message :to];
|
||||
[externalAPI sendEndpointTextMessage:to :message];
|
||||
}
|
||||
|
||||
- (void)toggleScreenShare {
|
||||
@@ -135,7 +135,7 @@ static void initializeViewsMap() {
|
||||
[externalAPI toggleScreenShare];
|
||||
}
|
||||
|
||||
- (void)retrieveParticipantsInfo:(void (^ _Nonnull)(NSArray * _Nullable))completionHandler {
|
||||
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completionHandler {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI retrieveParticipantsInfo:completionHandler];
|
||||
}
|
||||
@@ -150,9 +150,9 @@ static void initializeViewsMap() {
|
||||
[externalAPI closeChat];
|
||||
}
|
||||
|
||||
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendChatMessage:message :to];
|
||||
[externalAPI sendChatMessage:to :message];
|
||||
}
|
||||
|
||||
#pragma mark Private methods
|
||||
|
||||
@@ -239,19 +239,12 @@
|
||||
"muteEveryoneElseTitle": "Alle außer {{whom}} stummschalten?",
|
||||
"muteEveryoneDialog": "Wollen Sie wirklich alle stummschalten? Sie können deren Stummschaltung nicht mehr beenden, aber sie können ihre Stummschaltung jederzeit selbst beenden.",
|
||||
"muteEveryoneTitle": "Alle stummschalten?",
|
||||
"muteEveryoneElsesVideoDialog": "Sobald die Kamera deaktiviert ist, können Sie sie nicht wieder aktivieren, die Teilnehmer können dies aber jederzeit wieder ändern.",
|
||||
"muteEveryoneElsesVideoTitle": "Die Kamera von allen außer {{whom}} ausschalten?",
|
||||
"muteEveryonesVideoDialog": "Sind Sie sicher, dass Sie die Kamera von allen Teilnehmern deaktivieren möchten? Sie können sie nicht wieder aktivieren, die Teilnehmer können dies aber jederzeit wieder ändern.",
|
||||
"muteEveryonesVideoTitle": "Die Kamera von allen anderen ausschalten?",
|
||||
"muteEveryoneSelf": "sich selbst",
|
||||
"muteEveryoneStartMuted": "Alle beginnen von jetzt an stummgeschaltet",
|
||||
"muteParticipantBody": "Sie können die Stummschaltung anderer Personen nicht aufheben, aber eine Person kann ihre eigene Stummschaltung jederzeit beenden.",
|
||||
"muteParticipantButton": "Stummschalten",
|
||||
"muteParticipantDialog": "Wollen Sie diese Person wirklich stummschalten? Sie können die Stummschaltung nicht wieder aufheben, die Person kann dies aber jederzeit selbst tun.",
|
||||
"muteParticipantTitle": "Person stummschalten?",
|
||||
"muteParticipantsVideoButton": "Kamera ausschalten",
|
||||
"muteParticipantsVideoTitle": "Die Kamera von dieser Person ausschalten?",
|
||||
"muteParticipantsVideoBody": "Sie können die Kamera nicht wieder aktivieren, die Teilnehmer können dies aber jederzeit wieder ändern.",
|
||||
"Ok": "OK",
|
||||
"passwordLabel": "Dieses Meeting wurde gesichert. Bitte geben Sie das $t(lockRoomPasswordUppercase) ein, um dem Meeting beizutreten.",
|
||||
"passwordNotSupported": "Das Festlegen eines Konferenzpassworts wird nicht unterstützt.",
|
||||
@@ -491,8 +484,6 @@
|
||||
"mutedTitle": "Stummschaltung aktiv!",
|
||||
"mutedRemotelyTitle": "Sie wurden von {{participantDisplayName}} stummgeschaltet!",
|
||||
"mutedRemotelyDescription": "Sie können jederzeit die Stummschaltung aufheben, wenn Sie bereit sind zu sprechen. Wenn Sie fertig sind, können Sie sich wieder stummschalten, um Geräusche vom Meeting fernzuhalten.",
|
||||
"videoMutedRemotelyTitle": "Ihre Kamera wurde von {{participantDisplayName}} ausgeschaltet!",
|
||||
"videoMutedRemotelyDescription": "Sie können sie jederzeit wieder einschalten.",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) von einer anderen Person entfernt",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) von einer anderen Person gesetzt",
|
||||
"raisedHand": "{{name}} möchte sprechen.",
|
||||
@@ -723,16 +714,12 @@
|
||||
"moreOptions": "Menü „Weitere Optionen“",
|
||||
"mute": "„Audio stummschalten“ ein-/ausschalten",
|
||||
"muteEveryone": "Alle stummschalten",
|
||||
"muteEveryoneElse": "Alle anderen stummschalten",
|
||||
"muteEveryonesVideo": "Alle Kameras ausschalten",
|
||||
"muteEveryoneElsesVideo": "Alle anderen Kameras ausschalten",
|
||||
"pip": "Bild-in-Bild-Modus ein-/ausschalten",
|
||||
"privateMessage": "Private Nachricht senden",
|
||||
"profile": "Profil bearbeiten",
|
||||
"raiseHand": "„Melden“ ein-/ausschalten",
|
||||
"recording": "Aufzeichnung ein-/ausschalten",
|
||||
"remoteMute": "Personen stummschalten",
|
||||
"remoteVideoMute": "Kamera von dieser Person ausschalten",
|
||||
"security": "Sicherheitsoptionen",
|
||||
"Settings": "Einstellungen ein-/ausschalten",
|
||||
"sharedvideo": "YouTube-Videofreigabe ein-/ausschalten",
|
||||
@@ -777,7 +764,6 @@
|
||||
"moreOptions": "Weitere Optionen",
|
||||
"mute": "Stummschaltung aktivieren / deaktivieren",
|
||||
"muteEveryone": "Alle stummschalten",
|
||||
"muteEveryonesVideo": "Alle Kameras ausschalten",
|
||||
"noAudioSignalTitle": "Es kommt kein Input von Ihrem Mikrofon!",
|
||||
"noAudioSignalDesc": "Wenn Sie das Gerät nicht absichtlich über die Systemeinstellungen oder die Hardware stumm geschaltet haben, sollten Sie einen Wechsel des Geräts in Erwägung ziehen.",
|
||||
"noAudioSignalDescSuggestion": "Wenn Sie das Gerät nicht absichtlich über die Systemeinstellungen oder die Hardware stummgeschaltet haben, sollten Sie einen Wechsel auf das vorgeschlagene Gerät in Erwägung ziehen.",
|
||||
@@ -863,16 +849,13 @@
|
||||
},
|
||||
"videothumbnail": {
|
||||
"domute": "Stummschalten",
|
||||
"domuteVideo": "Kamera ausschalten",
|
||||
"domuteOthers": "Alle anderen stummschalten",
|
||||
"domuteVideoOfOthers": "Alle anderen Kameras auschalten",
|
||||
"flip": "Spiegeln",
|
||||
"grantModerator": "Moderationsrechte vergeben",
|
||||
"kick": "Hinauswerfen",
|
||||
"moderator": "Moderation",
|
||||
"mute": "Person ist stumm geschaltet",
|
||||
"muted": "Stummgeschaltet",
|
||||
"videoMuted": "Kamera ausgeschaltet",
|
||||
"remoteControl": "Fernsteuerung",
|
||||
"show": "Im Vordergrund anzeigen",
|
||||
"videomute": "Person hat die Kamera angehalten"
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
"today": "Today"
|
||||
},
|
||||
"chat": {
|
||||
"enter": "Enter chat room",
|
||||
"error": "Error: your message was not sent. Reason: {{error}}",
|
||||
"fieldPlaceHolder": "Type your message here",
|
||||
"messagebox": "Type a message",
|
||||
@@ -241,19 +240,12 @@
|
||||
"muteEveryoneElseTitle": "Mute everyone except {{whom}}?",
|
||||
"muteEveryoneDialog": "Are you sure you want to mute everyone? You won't be able to unmute them, but they can unmute themselves at any time.",
|
||||
"muteEveryoneTitle": "Mute everyone?",
|
||||
"muteEveryoneElsesVideoDialog": "Once the camera is disabled, you won't be able to turn it back on, but they can turn it back on at any time.",
|
||||
"muteEveryoneElsesVideoTitle": "Disable everyone's camera except {{whom}}?",
|
||||
"muteEveryonesVideoDialog": "Are you sure you want to disable everyone's camera? You won't be able to turn it back on, but they can turn it back on at any time.",
|
||||
"muteEveryonesVideoTitle": "Disable everyone's camera?",
|
||||
"muteEveryoneSelf": "yourself",
|
||||
"muteEveryoneStartMuted": "Everyone starts muted from now on",
|
||||
"muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
|
||||
"muteParticipantButton": "Mute",
|
||||
"muteParticipantDialog": "Are you sure you want to mute this participant? You won't be able to unmute them, but they can unmute themselves at any time.",
|
||||
"muteParticipantTitle": "Mute this participant?",
|
||||
"muteParticipantsVideoButton": "Disable camera",
|
||||
"muteParticipantsVideoTitle": "Disable camera of this participant?",
|
||||
"muteParticipantsVideoBody": "You won't be able to turn the camera back on, but they can turn it back on at any time.",
|
||||
"Ok": "OK",
|
||||
"passwordLabel": "The meeting has been locked by a participant. Please enter the $t(lockRoomPassword) to join.",
|
||||
"passwordNotSupported": "Setting a meeting $t(lockRoomPassword) is not supported.",
|
||||
@@ -491,8 +483,6 @@
|
||||
"mutedTitle": "You're muted!",
|
||||
"mutedRemotelyTitle": "You have been muted by {{participantDisplayName}}!",
|
||||
"mutedRemotelyDescription": "You can always unmute when you're ready to speak. Mute back when you're done to keep noise away from the meeting.",
|
||||
"videoMutedRemotelyTitle": "Your camera has been disabled by {{participantDisplayName}}!",
|
||||
"videoMutedRemotelyDescription": "You can always turn it on again.",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) removed by another participant",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) set by another participant",
|
||||
"raisedHand": "{{name}} would like to speak.",
|
||||
@@ -725,16 +715,12 @@
|
||||
"moreOptions": "Show more options",
|
||||
"mute": "Toggle mute audio",
|
||||
"muteEveryone": "Mute everyone",
|
||||
"muteEveryoneElse": "Mute everyone else",
|
||||
"muteEveryonesVideo": "Disable everyone's camera",
|
||||
"muteEveryoneElsesVideo": "Disable everyone else's camera",
|
||||
"pip": "Toggle Picture-in-Picture mode",
|
||||
"privateMessage": "Send private message",
|
||||
"profile": "Edit your profile",
|
||||
"raiseHand": "Toggle raise hand",
|
||||
"recording": "Toggle recording",
|
||||
"remoteMute": "Mute participant",
|
||||
"remoteVideoMute": "Disable camera of participant",
|
||||
"security": "Security options",
|
||||
"Settings": "Toggle settings",
|
||||
"sharedvideo": "Toggle Youtube video sharing",
|
||||
@@ -779,7 +765,6 @@
|
||||
"moreOptions": "More options",
|
||||
"mute": "Mute / Unmute",
|
||||
"muteEveryone": "Mute everyone",
|
||||
"muteEveryonesVideo": "Disable everyone's camera",
|
||||
"noAudioSignalTitle": "There is no input coming from your mic!",
|
||||
"noAudioSignalDesc": "If you did not purposely mute it from system settings or hardware, consider switching the device.",
|
||||
"noAudioSignalDescSuggestion": "If you did not purposely mute it from system settings or hardware, consider switching to the suggested device.",
|
||||
@@ -864,16 +849,13 @@
|
||||
"videothumbnail": {
|
||||
"connectionInfo": "Connection Info",
|
||||
"domute": "Mute",
|
||||
"domuteVideo": "Disable camera",
|
||||
"domuteOthers": "Mute everyone else",
|
||||
"domuteVideoOfOthers": "Disable camera of everyone else",
|
||||
"flip": "Flip",
|
||||
"grantModerator": "Grant Moderator",
|
||||
"kick": "Kick out",
|
||||
"moderator": "Moderator",
|
||||
"mute": "Participant is muted",
|
||||
"muted": "Muted",
|
||||
"videoMuted": "Camera disabled",
|
||||
"remoteControl": "Start / Stop remote control",
|
||||
"show": "Show on stage",
|
||||
"videomute": "Participant has stopped the camera"
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
} from '../../react/features/base/conference';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
|
||||
import JitsiMeetJS, { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../../react/features/base/media';
|
||||
import { pinParticipant, getParticipantById, kickParticipant } from '../../react/features/base/participants';
|
||||
import { setPrivateMessageRecipient } from '../../react/features/chat/actions';
|
||||
import { openChat } from '../../react/features/chat/actions.web';
|
||||
@@ -80,9 +79,7 @@ function initCommands() {
|
||||
sendAnalytics(createApiEvent('display.name.changed'));
|
||||
APP.conference.changeLocalDisplayName(displayName);
|
||||
},
|
||||
'mute-everyone': mediaType => {
|
||||
const muteMediaType = mediaType ? mediaType : MEDIA_TYPE.AUDIO;
|
||||
|
||||
'mute-everyone': () => {
|
||||
sendAnalytics(createApiEvent('muted-everyone'));
|
||||
const participants = APP.store.getState()['features/base/participants'];
|
||||
const localIds = participants
|
||||
@@ -90,7 +87,7 @@ function initCommands() {
|
||||
.filter(participant => participant.role === 'moderator')
|
||||
.map(participant => participant.id);
|
||||
|
||||
APP.store.dispatch(muteAllParticipants(localIds, muteMediaType));
|
||||
APP.store.dispatch(muteAllParticipants(localIds));
|
||||
},
|
||||
'toggle-lobby': isLobbyEnabled => {
|
||||
APP.store.dispatch(toggleLobbyMode(isLobbyEnabled));
|
||||
|
||||
108
package-lock.json
generated
108
package-lock.json
generated
@@ -3781,6 +3781,68 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@tensorflow-models/body-pix": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@tensorflow-models/body-pix/-/body-pix-2.0.4.tgz",
|
||||
"integrity": "sha512-wRoEZBv2BNORDZjNNRLu1W4Td4/LRbaqSJFVWryHeBcHr5m5xSSETwnDfFRtLLofFtVNHfi7VUZ7TFjkaktNug=="
|
||||
},
|
||||
"@tensorflow/tfjs": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-1.5.1.tgz",
|
||||
"integrity": "sha512-WiE+JQ3ibr5LibGiBz6HWUqLJW8HiX6ywUSCA7ehZ67vFsw4mPuVjv0WEEUfD/l47PkXYVAmWd+RYOJiuZC7Eg==",
|
||||
"requires": {
|
||||
"@tensorflow/tfjs-converter": "1.5.1",
|
||||
"@tensorflow/tfjs-core": "1.5.1",
|
||||
"@tensorflow/tfjs-data": "1.5.1",
|
||||
"@tensorflow/tfjs-layers": "1.5.1"
|
||||
}
|
||||
},
|
||||
"@tensorflow/tfjs-converter": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-1.5.1.tgz",
|
||||
"integrity": "sha512-M9tl2/ep8ntcZpmncHwKuvThsS7TaUWqJ9vJSgJmkazwTfAvlAJmZ8/p1miJ+m5sH1EJO4oTjiEmch6g8IA5IQ=="
|
||||
},
|
||||
"@tensorflow/tfjs-core": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-1.5.1.tgz",
|
||||
"integrity": "sha512-N4fsi8mLsRwRs8UJN2cARB1rYFxyVXkLyZ4wOusiR976BwwZbCwQrTTSIPzPqYT3rwiexEUzm7sM6ZaDl5dpXA==",
|
||||
"requires": {
|
||||
"@types/offscreencanvas": "~2019.3.0",
|
||||
"@types/seedrandom": "2.4.27",
|
||||
"@types/webgl-ext": "0.0.30",
|
||||
"@types/webgl2": "0.0.4",
|
||||
"node-fetch": "~2.1.2",
|
||||
"seedrandom": "2.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-fetch": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
|
||||
"integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@tensorflow/tfjs-data": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-1.5.1.tgz",
|
||||
"integrity": "sha512-eu4X0tHS1Tng+cvMO9gkMhUWX/UZQ//VpiaZfQJfa3zvUgxw6s1MHJFb0JC1T1FOnEgDVriZ8G758ysJZOybog==",
|
||||
"requires": {
|
||||
"@types/node-fetch": "^2.1.2",
|
||||
"node-fetch": "~2.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-fetch": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
|
||||
"integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@tensorflow/tfjs-layers": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-1.5.1.tgz",
|
||||
"integrity": "sha512-DyuhifqflK+bdpBRLAj3RuWm1eTVe8yNX2+WH+W+wmhpjGg7Yagnar6/66JdS2h3WUFoiplCpZRAVMVw631E5g=="
|
||||
},
|
||||
"@types/color-name": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
@@ -3840,8 +3902,20 @@
|
||||
"@types/node": {
|
||||
"version": "12.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz",
|
||||
"integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w=="
|
||||
},
|
||||
"@types/node-fetch": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.4.tgz",
|
||||
"integrity": "sha512-Oz6id++2qAOFuOlE1j0ouk1dzl3mmI1+qINPNBhi9nt/gVOz0G+13Ao6qjhdF0Ys+eOkhu6JnFmt38bR3H0POQ==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/offscreencanvas": {
|
||||
"version": "2019.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz",
|
||||
"integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q=="
|
||||
},
|
||||
"@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
@@ -3853,11 +3927,26 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz",
|
||||
"integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug=="
|
||||
},
|
||||
"@types/seedrandom": {
|
||||
"version": "2.4.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.27.tgz",
|
||||
"integrity": "sha1-nbVjk33YaRX2kJK8QyWdL0hXjkE="
|
||||
},
|
||||
"@types/stack-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
||||
"integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw=="
|
||||
},
|
||||
"@types/webgl-ext": {
|
||||
"version": "0.0.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz",
|
||||
"integrity": "sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg=="
|
||||
},
|
||||
"@types/webgl2": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz",
|
||||
"integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw=="
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "13.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.8.tgz",
|
||||
@@ -10254,8 +10343,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#4c668023b38c92615cd06c4e90006246c3e4aa2c",
|
||||
"from": "github:jitsi/lib-jitsi-meet#4c668023b38c92615cd06c4e90006246c3e4aa2c",
|
||||
"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",
|
||||
@@ -10351,9 +10440,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
"version": "4.17.19",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
|
||||
},
|
||||
"lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
@@ -14808,6 +14897,11 @@
|
||||
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.3.0.tgz",
|
||||
"integrity": "sha1-V6lXWUIEHYV3qGnXx01MOgvYiPY="
|
||||
},
|
||||
"seedrandom": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz",
|
||||
"integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw="
|
||||
},
|
||||
"select-hose": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
"@react-native-community/google-signin": "3.0.1",
|
||||
"@react-native-community/netinfo": "4.1.5",
|
||||
"@svgr/webpack": "4.3.2",
|
||||
"@tensorflow-models/body-pix": "2.0.4",
|
||||
"@tensorflow/tfjs": "1.5.1",
|
||||
"amplitude-js": "7.3.3",
|
||||
"base64-js": "1.3.1",
|
||||
"bc-css-flags": "3.0.0",
|
||||
@@ -54,9 +56,9 @@
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#4c668023b38c92615cd06c4e90006246c3e4aa2c",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#c534f748849a308d08b06e306f5a66709ccae056",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.21",
|
||||
"lodash": "4.17.19",
|
||||
"moment": "2.19.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
||||
|
||||
@@ -504,17 +504,15 @@ export function createRejoinedEvent({ url, lastConferenceDuration, timeSinceLeft
|
||||
*
|
||||
* @param {string} participantId - The ID of the participant that was remotely
|
||||
* muted.
|
||||
* @param {string} mediaType - The media type of the channel to mute.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createRemoteMuteConfirmedEvent(participantId, mediaType) {
|
||||
export function createRemoteMuteConfirmedEvent(participantId) {
|
||||
return {
|
||||
action: 'clicked',
|
||||
actionSubject: 'remote.mute.dialog.confirm.button',
|
||||
attributes: {
|
||||
'participant_id': participantId,
|
||||
'media_type': mediaType
|
||||
'participant_id': participantId
|
||||
},
|
||||
source: 'remote.mute.dialog',
|
||||
type: TYPE_UI
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Icon, IconCheck, IconCopy } from '../../base/icons';
|
||||
import { translate } from '../i18n';
|
||||
import { copyText } from '../util';
|
||||
import { copyText } from '../../base/util';
|
||||
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -149,9 +149,9 @@ function _addConferenceListeners(conference, dispatch) {
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.TRACK_MUTE_CHANGED,
|
||||
(track, participantThatMutedUs) => {
|
||||
(_, participantThatMutedUs) => {
|
||||
if (participantThatMutedUs) {
|
||||
dispatch(participantMutedUs(participantThatMutedUs, track));
|
||||
dispatch(participantMutedUs(participantThatMutedUs));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -53,11 +53,6 @@ type Props = {
|
||||
*/
|
||||
hideCancelButton: boolean,
|
||||
|
||||
/**
|
||||
* If true, no footer will be displayed.
|
||||
*/
|
||||
disableFooter?: boolean,
|
||||
|
||||
i18n: Object,
|
||||
|
||||
/**
|
||||
@@ -179,10 +174,6 @@ class StatelessDialog extends Component<Props> {
|
||||
this._renderCancelButton()
|
||||
].filter(Boolean);
|
||||
|
||||
if (this.props.disableFooter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalFooter showKeyline = { propsFromModalFooter.showKeyline } >
|
||||
{
|
||||
|
||||
@@ -11,16 +11,6 @@ export function isMobileBrowser() {
|
||||
return Platform.OS === 'android' || Platform.OS === 'ios';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not the current environment is an ios mobile device.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isIosMobileBrowser() {
|
||||
return Platform.OS === 'ios';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the chrome extensions defined in the config file are installed or not.
|
||||
*
|
||||
|
||||
@@ -68,14 +68,11 @@ export { default as IconMicrophoneEmpty } from './microphone-empty.svg';
|
||||
export { default as IconModerator } from './star.svg';
|
||||
export { default as IconMuteEveryone } from './mute-everyone.svg';
|
||||
export { default as IconMuteEveryoneElse } from './mute-everyone-else.svg';
|
||||
export { default as IconMuteVideoEveryone } from './mute-video-everyone.svg';
|
||||
export { default as IconMuteVideoEveryoneElse } from './mute-video-everyone-else.svg';
|
||||
export { default as IconNotificationJoin } from './navigate_next.svg';
|
||||
export { default as IconOpenInNew } from './open_in_new.svg';
|
||||
export { default as IconOutlook } from './office365.svg';
|
||||
export { default as IconPhone } from './phone.svg';
|
||||
export { default as IconPin } from './enlarge.svg';
|
||||
export { default as IconPlane } from './paper-plane.svg';
|
||||
export { default as IconPresentation } from './presentation.svg';
|
||||
export { default as IconRaisedHand } from './raised-hand.svg';
|
||||
export { default as IconRec } from './rec.svg';
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="#FFFFFF" d="M3.136,7.1l14.448,14.447l-1.033,1.032l-2.598-2.599c-0.115,0.077-0.307,0.153-0.459,0.153H3.709
|
||||
c-0.458,0-0.803-0.346-0.803-0.804v-8.179c0-0.459,0.344-0.803,0.803-0.803h0.612L2.104,8.131L3.136,7.1z M17.584,10.769v8.714
|
||||
l-9.135-9.134h5.045c0.459,0,0.84,0.344,0.84,0.803v2.866L17.584,10.769z"/>
|
||||
<path fill="#FFFFFF" d="M14.688,0.818l8.164,8.165l-0.584,0.583L20.8,8.098c-0.065,0.043-0.174,0.086-0.26,0.086h-5.528
|
||||
c-0.259,0-0.454-0.195-0.454-0.454V3.108c0-0.26,0.195-0.454,0.454-0.454h0.345l-1.253-1.253L14.688,0.818z M22.852,2.892v4.924
|
||||
l-5.162-5.162h2.851c0.26,0,0.476,0.194,0.476,0.454v1.619L22.852,2.892z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="#A4B8D1" d="M3.136,7.1l14.448,14.447l-1.033,1.032l-2.598-2.599c-0.115,0.077-0.307,0.153-0.459,0.153H3.709
|
||||
c-0.458,0-0.803-0.346-0.803-0.804v-8.179c0-0.459,0.344-0.803,0.803-0.803h0.612L2.104,8.131L3.136,7.1z M17.584,10.769v8.714
|
||||
l-9.135-9.134h5.045c0.459,0,0.84,0.344,0.84,0.803v2.866L17.584,10.769z"/>
|
||||
<path fill="#A4B8D1" d="M14.688,0.818l8.164,8.165l-0.584,0.583L20.8,8.098c-0.065,0.043-0.174,0.086-0.26,0.086h-5.528
|
||||
c-0.259,0-0.454-0.195-0.454-0.454V3.108c0-0.26,0.195-0.454,0.454-0.454h0.345l-1.253-1.253L14.688,0.818z M22.852,2.892v4.924
|
||||
l-5.162-5.162h2.851c0.26,0,0.476,0.194,0.476,0.454v1.619L22.852,2.892z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.6667 1.66663L1.66669 10.8333L7.6326 11.8276L8.33335 17.0833L10.644 13.2323L16.6667 17.9166V1.66663ZM8.73722 10.3221L6.35041 9.92426L15 4.63839V14.5089L11.3161 11.6436L12.5 7.49996L8.73722 10.3221Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 369 B |
@@ -16,9 +16,7 @@ import {
|
||||
PIN_PARTICIPANT,
|
||||
SET_LOADABLE_AVATAR_URL
|
||||
} from './actionTypes';
|
||||
import {
|
||||
DISCO_REMOTE_CONTROL_FEATURE
|
||||
} from './constants';
|
||||
import { DISCO_REMOTE_CONTROL_FEATURE } from './constants';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getNormalizedDisplayName,
|
||||
@@ -194,18 +192,15 @@ export function localParticipantRoleChanged(role) {
|
||||
* Create an action for muting another participant in the conference.
|
||||
*
|
||||
* @param {string} id - Participant's ID.
|
||||
* @param {MEDIA_TYPE} mediaType - The media to mute.
|
||||
* @returns {{
|
||||
* type: MUTE_REMOTE_PARTICIPANT,
|
||||
* id: string,
|
||||
* mediaType: MEDIA_TYPE
|
||||
* id: string
|
||||
* }}
|
||||
*/
|
||||
export function muteRemoteParticipant(id, mediaType) {
|
||||
export function muteRemoteParticipant(id) {
|
||||
return {
|
||||
type: MUTE_REMOTE_PARTICIPANT,
|
||||
id,
|
||||
mediaType
|
||||
id
|
||||
};
|
||||
}
|
||||
|
||||
@@ -455,20 +450,17 @@ export function participantUpdated(participant = {}) {
|
||||
* Action to signal that a participant has muted us.
|
||||
*
|
||||
* @param {JitsiParticipant} participant - Information about participant.
|
||||
* @param {JitsiLocalTrack} track - Information about the track that has been muted.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function participantMutedUs(participant, track) {
|
||||
export function participantMutedUs(participant) {
|
||||
return (dispatch, getState) => {
|
||||
if (!participant) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isAudio = track.isAudioTrack();
|
||||
|
||||
dispatch(showNotification({
|
||||
descriptionKey: isAudio ? 'notify.mutedRemotelyDescription' : 'notify.videoMutedRemotelyDescription',
|
||||
titleKey: isAudio ? 'notify.mutedRemotelyTitle' : 'notify.videoMutedRemotelyTitle',
|
||||
descriptionKey: 'notify.mutedRemotelyDescription',
|
||||
titleKey: 'notify.mutedRemotelyTitle',
|
||||
titleArguments: {
|
||||
participantDisplayName:
|
||||
getParticipantDisplayName(getState, participant.getId())
|
||||
|
||||
@@ -112,7 +112,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case MUTE_REMOTE_PARTICIPANT: {
|
||||
const { conference } = store.getState()['features/base/conference'];
|
||||
|
||||
conference.muteParticipant(action.id, action.mediaType);
|
||||
conference.muteParticipant(action.id);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ export function createLocalTracksA(options = {}) {
|
||||
options.facingMode || CAMERA_FACING_MODE.USER,
|
||||
micDeviceId: options.micDeviceId
|
||||
},
|
||||
/* firePermissionPromptIsShownEvent */ false,
|
||||
store)
|
||||
.then(
|
||||
localTracks => {
|
||||
|
||||
@@ -63,25 +63,16 @@ export async function createLocalPresenterTrack(options, desktopHeight) {
|
||||
* @param {string|null} [options.micDeviceId] - Microphone device id or
|
||||
* {@code undefined} to use app's settings.
|
||||
* @param {number|undefined} [oprions.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
|
||||
* @param {boolean} [options.firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
|
||||
* @param {boolean} [firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
|
||||
* should check for a {@code getUserMedia} permission prompt and fire a
|
||||
* corresponding event.
|
||||
* @param {boolean} [options.fireSlowPromiseEvent] - Whether lib-jitsi-meet
|
||||
* should check for a slow {@code getUserMedia} request and fire a
|
||||
* corresponding event.
|
||||
* @param {Object} store - The redux store in the context of which the function
|
||||
* is to execute and from which state such as {@code config} is to be retrieved.
|
||||
* @returns {Promise<JitsiLocalTrack[]>}
|
||||
*/
|
||||
export function createLocalTracksF(options = {}, store) {
|
||||
export function createLocalTracksF(options = {}, firePermissionPromptIsShownEvent, store) {
|
||||
let { cameraDeviceId, micDeviceId } = options;
|
||||
const {
|
||||
desktopSharingSourceDevice,
|
||||
desktopSharingSources,
|
||||
firePermissionPromptIsShownEvent,
|
||||
fireSlowPromiseEvent,
|
||||
timeout
|
||||
} = options;
|
||||
const { desktopSharingSourceDevice, desktopSharingSources, timeout } = options;
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
// TODO The app's settings should go in the redux store and then the
|
||||
@@ -123,12 +114,11 @@ export function createLocalTracksF(options = {}, store) {
|
||||
devices: options.devices.slice(0),
|
||||
effects,
|
||||
firefox_fake_device, // eslint-disable-line camelcase
|
||||
firePermissionPromptIsShownEvent,
|
||||
fireSlowPromiseEvent,
|
||||
micDeviceId,
|
||||
resolution,
|
||||
timeout
|
||||
})
|
||||
},
|
||||
firePermissionPromptIsShownEvent)
|
||||
.catch(err => {
|
||||
logger.error('Failed to create local tracks', options.devices, err);
|
||||
|
||||
@@ -171,10 +161,7 @@ export function createPrejoinTracks() {
|
||||
// Resolve with no tracks
|
||||
tryCreateLocalTracks = Promise.resolve([]);
|
||||
} else {
|
||||
tryCreateLocalTracks = createLocalTracksF({
|
||||
devices: initialDevices,
|
||||
firePermissionPromptIsShownEvent: true
|
||||
})
|
||||
tryCreateLocalTracks = createLocalTracksF({ devices: initialDevices }, true)
|
||||
.catch(err => {
|
||||
if (requestedAudio && requestedVideo) {
|
||||
|
||||
@@ -182,10 +169,7 @@ export function createPrejoinTracks() {
|
||||
errors.audioAndVideoError = err;
|
||||
|
||||
return (
|
||||
createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
firePermissionPromptIsShownEvent: true
|
||||
}));
|
||||
createLocalTracksF({ devices: [ 'audio' ] }, true));
|
||||
} else if (requestedAudio && !requestedVideo) {
|
||||
errors.audioOnlyError = err;
|
||||
|
||||
@@ -206,10 +190,7 @@ export function createPrejoinTracks() {
|
||||
|
||||
// Try video only...
|
||||
return requestedVideo
|
||||
? createLocalTracksF({
|
||||
devices: [ 'video' ],
|
||||
firePermissionPromptIsShownEvent: true
|
||||
})
|
||||
? createLocalTracksF({ devices: [ 'video' ] }, true)
|
||||
: [];
|
||||
})
|
||||
.catch(err => {
|
||||
|
||||
@@ -31,15 +31,24 @@ export function assignIfDefined(target: Object, source: Object) {
|
||||
* @param {string} textToCopy - Text to be copied.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export async function copyText(textToCopy: string) {
|
||||
export function copyText(textToCopy: string) {
|
||||
const fakeTextArea = document.createElement('textarea');
|
||||
let result;
|
||||
|
||||
// $FlowFixMe
|
||||
document.body.appendChild(fakeTextArea);
|
||||
fakeTextArea.value = textToCopy;
|
||||
fakeTextArea.select();
|
||||
|
||||
try {
|
||||
result = await navigator.clipboard.writeText(textToCopy);
|
||||
result = document.execCommand('copy');
|
||||
} catch (err) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
document.body.removeChild(fakeTextArea);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { createVideoBlurEvent, sendAnalytics } from '../../analytics';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { IconBlurBackground } from '../../base/icons';
|
||||
import { connect } from '../../base/redux';
|
||||
import { AbstractButton } from '../../base/toolbox/components';
|
||||
import { AbstractButton, BetaTag } from '../../base/toolbox/components';
|
||||
import type { AbstractButtonProps } from '../../base/toolbox/components';
|
||||
import { isLocalCameraTrackMuted } from '../../base/tracks';
|
||||
import { toggleBlurEffect } from '../actions';
|
||||
|
||||
/**
|
||||
@@ -19,11 +20,6 @@ type Props = AbstractButtonProps & {
|
||||
*/
|
||||
_isVideoBlurred: boolean,
|
||||
|
||||
/**
|
||||
* Whether video is currently muted or not.
|
||||
*/
|
||||
_videoMuted: boolean,
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
@@ -41,6 +37,18 @@ class VideoBlurButton extends AbstractButton<Props, *> {
|
||||
tooltip = 'toolbar.startvideoblur';
|
||||
toggledLabel = 'toolbar.stopvideoblur';
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which returns
|
||||
* a React Element to display (a beta tag) at the end of the button.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_getElementAfter() {
|
||||
return <BetaTag />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and toggles the blur effect
|
||||
* state accordingly.
|
||||
@@ -66,17 +74,6 @@ class VideoBlurButton extends AbstractButton<Props, *> {
|
||||
_isToggled() {
|
||||
return this.props._isVideoBlurred;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code boolean} value indicating if disabled state is
|
||||
* enabled or not.
|
||||
*
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isDisabled() {
|
||||
return this.props._videoMuted;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,13 +87,9 @@ class VideoBlurButton extends AbstractButton<Props, *> {
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state): Object {
|
||||
const tracks = state['features/base/tracks'];
|
||||
|
||||
return {
|
||||
_isVideoBlurred: Boolean(state['features/blur'].blurEnabled),
|
||||
_videoMuted: isLocalCameraTrackMuted(tracks)
|
||||
_isVideoBlurred: Boolean(state['features/blur'].blurEnabled)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(VideoBlurButton));
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
import { getJitsiMeetGlobalNS, loadScript } from '../base/util';
|
||||
|
||||
let filterSupport;
|
||||
|
||||
/**
|
||||
* Returns promise that resolves with the blur effect instance.
|
||||
*
|
||||
@@ -18,21 +16,3 @@ export function getBlurEffect() {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -20,10 +20,9 @@ export * from './actions.any';
|
||||
*/
|
||||
export function openChat(participant: Object) {
|
||||
return function(dispatch: (Object) => Object) {
|
||||
dispatch({
|
||||
participant,
|
||||
type: OPEN_CHAT
|
||||
});
|
||||
dispatch({ participant,
|
||||
type: OPEN_CHAT });
|
||||
VideoLayout.onResize();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,8 +40,5 @@ export function toggleChat() {
|
||||
} else {
|
||||
dispatch(openChat());
|
||||
}
|
||||
|
||||
// Recompute the large video size whenever we toggle the chat, as it takes chat state into account.
|
||||
VideoLayout.onResize();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { toggleChat } from '../../actions.web';
|
||||
import AbstractChat, {
|
||||
_mapStateToProps,
|
||||
type Props
|
||||
@@ -13,10 +14,8 @@ import ChatDialog from './ChatDialog';
|
||||
import Header from './ChatDialogHeader';
|
||||
import ChatInput from './ChatInput';
|
||||
import DisplayNameForm from './DisplayNameForm';
|
||||
import KeyboardAvoider from './KeyboardAvoider';
|
||||
import MessageContainer from './MessageContainer';
|
||||
import MessageRecipient from './MessageRecipient';
|
||||
import TouchmoveHack from './TouchmoveHack';
|
||||
|
||||
/**
|
||||
* React Component for holding the chat feature in a side panel that slides in
|
||||
@@ -51,6 +50,7 @@ class Chat extends AbstractChat<Props> {
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._renderPanelContent = this._renderPanelContent.bind(this);
|
||||
this._onChatInputResize = this._onChatInputResize.bind(this);
|
||||
this._onToggleChat = this._onToggleChat.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,16 +112,13 @@ class Chat extends AbstractChat<Props> {
|
||||
_renderChat() {
|
||||
return (
|
||||
<>
|
||||
<TouchmoveHack isModal = { this.props._isModal }>
|
||||
<MessageContainer
|
||||
messages = { this.props._messages }
|
||||
ref = { this._messageContainerRef } />
|
||||
</TouchmoveHack>
|
||||
<MessageContainer
|
||||
messages = { this.props._messages }
|
||||
ref = { this._messageContainerRef } />
|
||||
<MessageRecipient />
|
||||
<ChatInput
|
||||
onResize = { this._onChatInputResize }
|
||||
onSend = { this._onSendMessage } />
|
||||
<KeyboardAvoider />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -135,7 +132,9 @@ class Chat extends AbstractChat<Props> {
|
||||
*/
|
||||
_renderChatHeader() {
|
||||
return (
|
||||
<Header className = 'chat-header' />
|
||||
<Header
|
||||
className = 'chat-header'
|
||||
onCancel = { this._onToggleChat } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -199,6 +198,18 @@ class Chat extends AbstractChat<Props> {
|
||||
}
|
||||
|
||||
_onSendMessage: (string) => void;
|
||||
|
||||
_onToggleChat: () => void;
|
||||
|
||||
/**
|
||||
* Toggles the chat window.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onToggleChat() {
|
||||
this.props.dispatch(toggleChat());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(Chat));
|
||||
|
||||
@@ -24,7 +24,6 @@ function ChatDialog({ children }: Props) {
|
||||
<Dialog
|
||||
customHeader = { Header }
|
||||
disableEnter = { true }
|
||||
disableFooter = { true }
|
||||
hideCancelButton = { true }
|
||||
submitDisabled = { true }
|
||||
titleKey = 'chat.title'>
|
||||
|
||||
@@ -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 '../../actions.web';
|
||||
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));
|
||||
|
||||
@@ -6,7 +6,6 @@ import TextareaAutosize from 'react-textarea-autosize';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconPlane } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
|
||||
import SmileysPanel from './SmileysPanel';
|
||||
@@ -82,7 +81,6 @@ class ChatInput extends Component<Props, State> {
|
||||
this._onDetectSubmit = this._onDetectSubmit.bind(this);
|
||||
this._onMessageChange = this._onMessageChange.bind(this);
|
||||
this._onSmileySelect = this._onSmileySelect.bind(this);
|
||||
this._onSubmitMessage = this._onSubmitMessage.bind(this);
|
||||
this._onToggleSmileysPanel = this._onToggleSmileysPanel.bind(this);
|
||||
this._setTextAreaRef = this._setTextAreaRef.bind(this);
|
||||
}
|
||||
@@ -111,40 +109,31 @@ class ChatInput extends Component<Props, State> {
|
||||
? 'show-smileys' : 'hide-smileys'} smileys-panel`;
|
||||
|
||||
return (
|
||||
<div className = { `chat-input-container${this.state.message.trim().length ? ' populated' : ''}` }>
|
||||
<div id = 'chat-input' >
|
||||
<div className = 'smiley-input'>
|
||||
<div id = 'smileysarea'>
|
||||
<div id = 'smileys'>
|
||||
<Emoji
|
||||
onClick = { this._onToggleSmileysPanel }
|
||||
text = ':)' />
|
||||
</div>
|
||||
</div>
|
||||
<div className = { smileysPanelClassName }>
|
||||
<SmileysPanel
|
||||
onSmileySelect = { this._onSmileySelect } />
|
||||
<div id = 'chat-input' >
|
||||
<div className = 'smiley-input'>
|
||||
<div id = 'smileysarea'>
|
||||
<div id = 'smileys'>
|
||||
<Emoji
|
||||
onClick = { this._onToggleSmileysPanel }
|
||||
text = ':)' />
|
||||
</div>
|
||||
</div>
|
||||
<div className = 'usrmsg-form'>
|
||||
<TextareaAutosize
|
||||
id = 'usermsg'
|
||||
inputRef = { this._setTextAreaRef }
|
||||
maxRows = { 5 }
|
||||
onChange = { this._onMessageChange }
|
||||
onHeightChange = { this.props.onResize }
|
||||
onKeyDown = { this._onDetectSubmit }
|
||||
placeholder = { this.props.t('chat.messagebox') }
|
||||
value = { this.state.message } />
|
||||
</div>
|
||||
<div className = 'send-button-container'>
|
||||
<div
|
||||
className = 'send-button'
|
||||
onClick = { this._onSubmitMessage }>
|
||||
<Icon src = { IconPlane } />
|
||||
</div>
|
||||
<div className = { smileysPanelClassName }>
|
||||
<SmileysPanel
|
||||
onSmileySelect = { this._onSmileySelect } />
|
||||
</div>
|
||||
</div>
|
||||
<div className = 'usrmsg-form'>
|
||||
<TextareaAutosize
|
||||
id = 'usermsg'
|
||||
inputRef = { this._setTextAreaRef }
|
||||
maxRows = { 5 }
|
||||
onChange = { this._onMessageChange }
|
||||
onHeightChange = { this.props.onResize }
|
||||
onKeyDown = { this._onDetectSubmit }
|
||||
placeholder = { this.props.t('chat.messagebox') }
|
||||
value = { this.state.message } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -159,24 +148,6 @@ class ChatInput extends Component<Props, State> {
|
||||
this._textArea && this._textArea.focus();
|
||||
}
|
||||
|
||||
|
||||
_onSubmitMessage: () => void;
|
||||
|
||||
/**
|
||||
* Submits the message to the chat window.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSubmitMessage() {
|
||||
const trimmed = this.state.message.trim();
|
||||
|
||||
if (trimmed) {
|
||||
this.props.onSend(trimmed);
|
||||
|
||||
this.setState({ message: '' });
|
||||
}
|
||||
|
||||
}
|
||||
_onDetectSubmit: (Object) => void;
|
||||
|
||||
/**
|
||||
@@ -192,7 +163,13 @@ class ChatInput extends Component<Props, State> {
|
||||
&& event.shiftKey === false) {
|
||||
event.preventDefault();
|
||||
|
||||
this._onSubmitMessage();
|
||||
const trimmed = this.state.message.trim();
|
||||
|
||||
if (trimmed) {
|
||||
this.props.onSend(trimmed);
|
||||
|
||||
this.setState({ message: '' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { updateSettings } from '../../../base/settings';
|
||||
|
||||
import KeyboardAvoider from './KeyboardAvoider';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@DisplayNameForm}.
|
||||
*/
|
||||
@@ -72,24 +70,16 @@ class DisplayNameForm extends Component<Props, State> {
|
||||
|
||||
return (
|
||||
<div id = 'nickname'>
|
||||
<span>{ this.props.t('chat.nickname.title') }</span>
|
||||
<form onSubmit = { this._onSubmit }>
|
||||
<FieldTextStateless
|
||||
autoFocus = { true }
|
||||
compact = { true }
|
||||
id = 'nickinput'
|
||||
label = { t('chat.nickname.title') }
|
||||
onChange = { this._onDisplayNameChange }
|
||||
placeholder = { t('chat.nickname.popover') }
|
||||
shouldFitContainer = { true }
|
||||
type = 'text'
|
||||
value = { this.state.displayName } />
|
||||
</form>
|
||||
<div
|
||||
className = { `enter-chat${this.state.displayName.trim() ? '' : ' disabled'}` }
|
||||
onClick = { this._onSubmit }>
|
||||
{ t('chat.enter') }
|
||||
</div>
|
||||
<KeyboardAvoider />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { isIosMobileBrowser } from '../../../base/environment/utils';
|
||||
|
||||
const Avoider = styled.div`
|
||||
height: ${props => props.elementHeight}px;
|
||||
`;
|
||||
|
||||
/**
|
||||
* Component that renders an element to lift the chat input above the Safari keyboard,
|
||||
* computing the appropriate height comparisons based on the {@code visualViewport}.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function KeyboardAvoider() {
|
||||
if (!isIosMobileBrowser()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [ elementHeight, setElementHeight ] = useState(0);
|
||||
const [ storedHeight, setStoredHeight ] = useState(window.innerHeight);
|
||||
|
||||
/**
|
||||
* Handles the resizing of the visual viewport in order to compute
|
||||
* the {@code KeyboardAvoider}'s height.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleViewportResize() {
|
||||
const { innerWidth, visualViewport: { width, height } } = window;
|
||||
|
||||
// Compare the widths to make sure the {@code visualViewport} didn't resize due to zooming.
|
||||
if (width === innerWidth) {
|
||||
if (height < storedHeight) {
|
||||
setElementHeight(storedHeight - height);
|
||||
} else {
|
||||
setElementHeight(0);
|
||||
}
|
||||
setStoredHeight(height);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Call the handler in case the keyboard is open when the {@code KeyboardAvoider} is mounted.
|
||||
handleViewportResize();
|
||||
|
||||
window.visualViewport.addEventListener('resize', handleViewportResize);
|
||||
|
||||
return () => {
|
||||
window.visualViewport.removeEventListener('resize', handleViewportResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <Avoider elementHeight = { elementHeight } />;
|
||||
}
|
||||
|
||||
export default KeyboardAvoider;
|
||||
@@ -1,67 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The component(s) that need to be scrollable on mobile.
|
||||
*/
|
||||
children: React$Node,
|
||||
|
||||
/**
|
||||
* Whether the component is rendered within a modal.
|
||||
*/
|
||||
isModal: boolean
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Component that disables {@code touchmove} propagation below it.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function TouchmoveHack({ children, isModal }: Props) {
|
||||
if (!isModal || !isMobileBrowser()) {
|
||||
return children;
|
||||
}
|
||||
|
||||
const touchMoveElementRef = useRef(null);
|
||||
|
||||
/**
|
||||
* Atlaskit's {@code Modal} uses a third party library to disable touchmove events
|
||||
* which makes scrolling inside dialogs impossible. We therefore employ this hack
|
||||
* to intercept and stop the propagation of touchmove events from this wrapper that
|
||||
* is placed around the chat conversation from the {@code ChatDialog}.
|
||||
*
|
||||
* @param {Event} event - The touchmove event fired within the component.
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleTouchMove(event: TouchEvent) {
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (touchMoveElementRef && touchMoveElementRef.current) {
|
||||
touchMoveElementRef.current.addEventListener('touchmove', handleTouchMove, true);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (touchMoveElementRef && touchMoveElementRef.current) {
|
||||
touchMoveElementRef.current.removeEventListener('touchmove', handleTouchMove, true);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'touchmove-hack'
|
||||
ref = { touchMoveElementRef }>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TouchmoveHack;
|
||||
@@ -58,14 +58,6 @@ const DEFAULT_STATE = {
|
||||
*/
|
||||
defaultBranding: true,
|
||||
|
||||
/**
|
||||
* Url for a custom page for DID numbers list.
|
||||
*
|
||||
* @public
|
||||
* @type {string}
|
||||
*/
|
||||
didPageUrl: '',
|
||||
|
||||
/**
|
||||
* The custom invite domain.
|
||||
*
|
||||
@@ -109,7 +101,6 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
||||
backgroundColor,
|
||||
backgroundImageUrl,
|
||||
defaultBranding,
|
||||
didPageUrl,
|
||||
inviteDomain,
|
||||
logoClickUrl,
|
||||
logoImageUrl
|
||||
@@ -119,7 +110,6 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
||||
backgroundColor,
|
||||
backgroundImageUrl,
|
||||
defaultBranding,
|
||||
didPageUrl,
|
||||
inviteDomain,
|
||||
logoClickUrl,
|
||||
logoImageUrl,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// @flow
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { createInviteDialogEvent, sendAnalytics } from '../../../../analytics';
|
||||
import { getRoomName } from '../../../../base/conference';
|
||||
import { getInviteURL } from '../../../../base/connection';
|
||||
import { Dialog } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { JitsiRecordingConstants } from '../../../../base/lib-jitsi-meet';
|
||||
import { getLocalParticipant } from '../../../../base/participants';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { isVpaasMeeting } from '../../../../billing-counter/functions';
|
||||
import EmbedMeetingTrigger from '../../../../embed-meeting/components/EmbedMeetingTrigger';
|
||||
@@ -24,6 +26,11 @@ declare var interfaceConfig: Object;
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The name of the current conference. Used as part of inviting users.
|
||||
*/
|
||||
_conferenceName: string,
|
||||
|
||||
/**
|
||||
* The object representing the dialIn feature.
|
||||
*/
|
||||
@@ -34,11 +41,6 @@ type Props = {
|
||||
*/
|
||||
_embedMeetingVisible: boolean,
|
||||
|
||||
/**
|
||||
* The meeting invitation text.
|
||||
*/
|
||||
_invitationText: string,
|
||||
|
||||
/**
|
||||
* Whether or not invite contacts should be visible.
|
||||
*/
|
||||
@@ -55,9 +57,14 @@ type Props = {
|
||||
_liveStreamViewURL: string,
|
||||
|
||||
/**
|
||||
* The default phone number.
|
||||
* The redux representation of the local participant.
|
||||
*/
|
||||
_phoneNumber: ?string,
|
||||
_localParticipantName: ?string,
|
||||
|
||||
/**
|
||||
* The current location url of the conference.
|
||||
*/
|
||||
_locationUrl: Object,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
@@ -76,15 +83,17 @@ type Props = {
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
function AddPeopleDialog({
|
||||
_conferenceName,
|
||||
_dialIn,
|
||||
_embedMeetingVisible,
|
||||
_invitationText,
|
||||
_inviteContactsVisible,
|
||||
_inviteUrl,
|
||||
_liveStreamViewURL,
|
||||
_phoneNumber,
|
||||
_localParticipantName,
|
||||
_locationUrl,
|
||||
t,
|
||||
updateNumbers }: Props) {
|
||||
const [ phoneNumber, setPhoneNumber ] = useState(undefined);
|
||||
|
||||
/**
|
||||
* Updates the dial-in numbers.
|
||||
@@ -110,6 +119,27 @@ function AddPeopleDialog({
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Updates the phone number in the state once the dial-in numbers are fetched.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!phoneNumber && _dialIn && _dialIn.numbers) {
|
||||
setPhoneNumber(_getDefaultPhoneNumber(_dialIn.numbers));
|
||||
}
|
||||
}, [ _dialIn ]);
|
||||
|
||||
const invite = getInviteText({
|
||||
_conferenceName,
|
||||
_localParticipantName,
|
||||
_inviteUrl,
|
||||
_locationUrl,
|
||||
_dialIn,
|
||||
_liveStreamViewURL,
|
||||
phoneNumber,
|
||||
t
|
||||
});
|
||||
const inviteSubject = t('addPeople.inviteMoreMailSubject', {
|
||||
appName: interfaceConfig.APP_NAME
|
||||
});
|
||||
@@ -126,7 +156,7 @@ function AddPeopleDialog({
|
||||
<CopyMeetingLinkSection url = { _inviteUrl } />
|
||||
<InviteByEmailSection
|
||||
inviteSubject = { inviteSubject }
|
||||
inviteText = { _invitationText } />
|
||||
inviteText = { invite } />
|
||||
{ _embedMeetingVisible && <EmbedMeetingTrigger /> }
|
||||
<div className = 'invite-more-dialog separator' />
|
||||
{
|
||||
@@ -135,7 +165,11 @@ function AddPeopleDialog({
|
||||
}
|
||||
{
|
||||
_dialIn.numbers
|
||||
&& <DialInSection phoneNumber = { _phoneNumber } />
|
||||
&& <DialInSection
|
||||
conferenceName = { _conferenceName }
|
||||
dialIn = { _dialIn }
|
||||
locationUrl = { _locationUrl }
|
||||
phoneNumber = { phoneNumber } />
|
||||
}
|
||||
</div>
|
||||
</Dialog>
|
||||
@@ -147,32 +181,29 @@ function AddPeopleDialog({
|
||||
* {@code AddPeopleDialog} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Object} ownProps - The properties explicitly passed to the component.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function mapStateToProps(state, ownProps) {
|
||||
function mapStateToProps(state) {
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const currentLiveStreamingSession
|
||||
= getActiveSession(state, JitsiRecordingConstants.mode.STREAM);
|
||||
const { iAmRecorder } = state['features/base/config'];
|
||||
const addPeopleEnabled = isAddPeopleEnabled(state);
|
||||
const dialOutEnabled = isDialOutEnabled(state);
|
||||
const hideInviteContacts = iAmRecorder || (!addPeopleEnabled && !dialOutEnabled);
|
||||
const dialIn = state['features/invite'];
|
||||
const phoneNumber = dialIn && dialIn.numbers ? _getDefaultPhoneNumber(dialIn.numbers) : undefined;
|
||||
|
||||
return {
|
||||
_dialIn: dialIn,
|
||||
_conferenceName: getRoomName(state),
|
||||
_dialIn: state['features/invite'],
|
||||
_embedMeetingVisible: !isVpaasMeeting(state),
|
||||
_invitationText: getInviteText({ state,
|
||||
phoneNumber,
|
||||
t: ownProps.t }),
|
||||
_inviteContactsVisible: interfaceConfig.ENABLE_DIAL_OUT && !hideInviteContacts,
|
||||
_inviteUrl: getInviteURL(state),
|
||||
_liveStreamViewURL:
|
||||
currentLiveStreamingSession
|
||||
&& currentLiveStreamingSession.liveStreamViewURL,
|
||||
_phoneNumber: phoneNumber
|
||||
_localParticipantName: localParticipant?.name,
|
||||
_locationUrl: state['features/base/connection'].locationURL
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { getDialInfoPageURL } from '../../../functions';
|
||||
|
||||
import DialInNumber from './DialInNumber';
|
||||
@@ -11,14 +10,19 @@ import DialInNumber from './DialInNumber';
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The object representing the dialIn feature.
|
||||
* The name of the current conference. Used as part of inviting users.
|
||||
*/
|
||||
_dialIn: Object,
|
||||
conferenceName: string,
|
||||
|
||||
/**
|
||||
* The url of the page containing the dial-in numbers list.
|
||||
* The object representing the dialIn feature.
|
||||
*/
|
||||
_dialInfoPageUrl: string,
|
||||
dialIn: Object,
|
||||
|
||||
/**
|
||||
* The current location url of the conference.
|
||||
*/
|
||||
locationUrl: Object,
|
||||
|
||||
/**
|
||||
* The phone number to dial to begin the process of dialing into a
|
||||
@@ -41,19 +45,25 @@ type Props = {
|
||||
* @returns {null|ReactElement}
|
||||
*/
|
||||
function DialInSection({
|
||||
_dialIn,
|
||||
_dialInfoPageUrl,
|
||||
conferenceName,
|
||||
dialIn,
|
||||
locationUrl,
|
||||
phoneNumber,
|
||||
t
|
||||
}: Props) {
|
||||
return (
|
||||
<div className = 'invite-more-dialog dial-in-display'>
|
||||
<DialInNumber
|
||||
conferenceID = { _dialIn.conferenceID }
|
||||
conferenceID = { dialIn.conferenceID }
|
||||
phoneNumber = { phoneNumber } />
|
||||
<a
|
||||
className = 'more-numbers'
|
||||
href = { _dialInfoPageUrl }
|
||||
href = {
|
||||
getDialInfoPageURL(
|
||||
conferenceName,
|
||||
locationUrl
|
||||
)
|
||||
}
|
||||
rel = 'noopener noreferrer'
|
||||
target = '_blank'>
|
||||
{ t('info.moreNumbers') }
|
||||
@@ -62,20 +72,4 @@ function DialInSection({
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code DialInLink} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_dialIn: state['features/invite'],
|
||||
_dialInfoPageUrl: getDialInfoPageURL(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(DialInSection));
|
||||
export default translate(DialInSection);
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { getActiveSession } from '../../features/recording/functions';
|
||||
import { getRoomName } from '../base/conference';
|
||||
import { getInviteURL } from '../base/connection';
|
||||
import { i18next } from '../base/i18n';
|
||||
import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
|
||||
import { getLocalParticipant, isLocalParticipantModerator } from '../base/participants';
|
||||
import { isLocalParticipantModerator } from '../base/participants';
|
||||
import { toState } from '../base/redux';
|
||||
import { doGetJSON, parseURIString } from '../base/util';
|
||||
import { isVpaasMeeting } from '../billing-counter/functions';
|
||||
@@ -243,44 +239,43 @@ export function getInviteResultsForQuery(
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getInviteText({
|
||||
state,
|
||||
_conferenceName,
|
||||
_localParticipantName,
|
||||
_inviteUrl,
|
||||
_locationUrl,
|
||||
_dialIn,
|
||||
_liveStreamViewURL,
|
||||
phoneNumber,
|
||||
t
|
||||
}: Object) {
|
||||
const dialIn = state['features/invite'];
|
||||
const inviteUrl = getInviteURL(state);
|
||||
const currentLiveStreamingSession = getActiveSession(state, JitsiRecordingConstants.mode.STREAM);
|
||||
const liveStreamViewURL
|
||||
= currentLiveStreamingSession
|
||||
&& currentLiveStreamingSession.liveStreamViewURL;
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const localParticipantName = localParticipant?.name;
|
||||
const inviteURL = _decodeRoomURI(_inviteUrl);
|
||||
|
||||
const inviteURL = _decodeRoomURI(inviteUrl);
|
||||
|
||||
let invite = localParticipantName
|
||||
? t('info.inviteURLFirstPartPersonal', { name: localParticipantName })
|
||||
let invite = _localParticipantName
|
||||
? t('info.inviteURLFirstPartPersonal', { name: _localParticipantName })
|
||||
: t('info.inviteURLFirstPartGeneral');
|
||||
|
||||
invite += t('info.inviteURLSecondPart', {
|
||||
url: inviteURL
|
||||
});
|
||||
|
||||
if (liveStreamViewURL) {
|
||||
if (_liveStreamViewURL) {
|
||||
const liveStream = t('info.inviteLiveStream', {
|
||||
url: liveStreamViewURL
|
||||
url: _liveStreamViewURL
|
||||
});
|
||||
|
||||
invite = `${invite}\n${liveStream}`;
|
||||
}
|
||||
|
||||
if (shouldDisplayDialIn(dialIn)) {
|
||||
if (shouldDisplayDialIn(_dialIn)) {
|
||||
const dial = t('info.invitePhone', {
|
||||
number: phoneNumber,
|
||||
conferenceID: dialIn.conferenceID
|
||||
conferenceID: _dialIn.conferenceID
|
||||
});
|
||||
const moreNumbers = t('info.invitePhoneAlternatives', {
|
||||
url: getDialInfoPageURL(state),
|
||||
url: getDialInfoPageURL(
|
||||
_conferenceName,
|
||||
_locationUrl
|
||||
),
|
||||
silentUrl: `${inviteURL}#config.startSilent=true`
|
||||
});
|
||||
|
||||
@@ -519,7 +514,9 @@ export function getShareInfoText(
|
||||
.catch(error =>
|
||||
logger.error('Error fetching numbers or conferenceID', error))
|
||||
.then(defaultDialInNumber => {
|
||||
let dialInfoPageUrl = getDialInfoPageURL(state);
|
||||
let dialInfoPageUrl = getDialInfoPageURL(
|
||||
room,
|
||||
state['features/base/connection'].locationURL);
|
||||
|
||||
if (useHtml) {
|
||||
dialInfoPageUrl
|
||||
@@ -540,19 +537,28 @@ export function getShareInfoText(
|
||||
/**
|
||||
* Generates the URL for the static dial in info page.
|
||||
*
|
||||
* @param {Object} state - The state from the Redux store.
|
||||
* @param {string} conferenceName - The conference name.
|
||||
* @param {Object} locationURL - The current location URL, the object coming
|
||||
* from state ['features/base/connection'].locationURL.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getDialInfoPageURL(state: Object) {
|
||||
const { didPageUrl } = state['features/dynamic-branding'];
|
||||
const conferenceName = getRoomName(state);
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const { href } = locationURL;
|
||||
const room = _decodeRoomURI(conferenceName);
|
||||
export function getDialInfoPageURL(
|
||||
conferenceName: string,
|
||||
locationURL: Object) {
|
||||
const origin = locationURL.origin;
|
||||
const pathParts = locationURL.pathname.split('/');
|
||||
|
||||
const url = didPageUrl || `${href.substring(0, href.lastIndexOf('/'))}/static/dialInInfo.html`;
|
||||
pathParts.length = pathParts.length - 1;
|
||||
|
||||
return `${url}?room=${room}`;
|
||||
const newPath = pathParts.reduce((accumulator, currentValue) => {
|
||||
if (currentValue) {
|
||||
return `${accumulator}/${currentValue}`;
|
||||
}
|
||||
|
||||
return accumulator;
|
||||
}, '');
|
||||
|
||||
return `${origin}${newPath}/static/dialInInfo.html?room=${_decodeRoomURI(conferenceName)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getFirstLoadableAvatarUrl, getParticipantDisplayName } from '../base/pa
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
|
||||
import { isTestModeEnabled } from '../base/testing';
|
||||
import { NOTIFICATION_TYPE, showNotification } from '../notifications';
|
||||
import { shouldAutoKnock } from '../prejoin/functions';
|
||||
import { isPrejoinPageEnabled } from '../prejoin/functions';
|
||||
|
||||
import { KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED } from './actionTypes';
|
||||
import {
|
||||
@@ -100,7 +100,8 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
|
||||
|
||||
dispatch(openLobbyScreen());
|
||||
|
||||
if (shouldAutoKnock(state)) {
|
||||
if (isPrejoinPageEnabled(state) && !state['features/lobby'].knocking) {
|
||||
// prejoin is enabled, so we knock automatically
|
||||
dispatch(startKnocking());
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
getURLWithoutParams
|
||||
} from '../../base/connection';
|
||||
import { JitsiConferenceEvents } from '../../base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../../base/media';
|
||||
import { SET_AUDIO_MUTED } from '../../base/media/actionTypes';
|
||||
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT, getParticipants, getParticipantById } from '../../base/participants';
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../../base/redux';
|
||||
@@ -271,7 +270,7 @@ function _registerForNativeEvents(store) {
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.SET_AUDIO_MUTED, ({ muted }) => {
|
||||
dispatch(muteLocal(muted === 'true', MEDIA_TYPE.AUDIO));
|
||||
dispatch(muteLocal(muted === 'true'));
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.SEND_ENDPOINT_TEXT_MESSAGE, ({ to, message }) => {
|
||||
|
||||
@@ -11,16 +11,22 @@ import { getDialInfoPageURL, shouldDisplayDialIn } from '../../invite';
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The name of the current conference.
|
||||
*/
|
||||
_room: string,
|
||||
|
||||
/**
|
||||
* The current location url of the conference.
|
||||
*/
|
||||
_locationURL: string,
|
||||
|
||||
|
||||
/**
|
||||
* The redux state representing the dial-in numbers feature.
|
||||
*/
|
||||
_dialIn: Object,
|
||||
|
||||
/**
|
||||
* The url of the page containing the dial-in numbers list.
|
||||
*/
|
||||
_dialInfoPageUrl: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
@@ -41,7 +47,7 @@ class DialInLink extends Component<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _dialIn, _dialInfoPageUrl, t } = this.props;
|
||||
const { _room, _locationURL, _dialIn, t } = this.props;
|
||||
|
||||
if (!shouldDisplayDialIn(_dialIn)) {
|
||||
return null;
|
||||
@@ -50,7 +56,12 @@ class DialInLink extends Component<Props> {
|
||||
return (
|
||||
<div>{t('toolbar.noAudioSignalDialInDesc')}
|
||||
<a
|
||||
href = { _dialInfoPageUrl }
|
||||
href = {
|
||||
getDialInfoPageURL(
|
||||
_room,
|
||||
_locationURL
|
||||
)
|
||||
}
|
||||
rel = 'noopener noreferrer'
|
||||
target = '_blank'>
|
||||
{t('toolbar.noAudioSignalDialInLinkDesc')}
|
||||
@@ -66,12 +77,18 @@ class DialInLink extends Component<Props> {
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
* @returns {{
|
||||
* _room: string,
|
||||
* _locationURL: string,
|
||||
* _dialIn: Object,
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
|
||||
return {
|
||||
_dialIn: state['features/invite'],
|
||||
_dialInfoPageUrl: getDialInfoPageURL(state)
|
||||
_room: state['features/base/conference'].room,
|
||||
_locationURL: state['features/base/connection'].locationURL,
|
||||
_dialIn: state['features/invite']
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -14,17 +14,6 @@
|
||||
export const MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED
|
||||
= 'MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED';
|
||||
|
||||
/**
|
||||
* The type of the Redux action which signals that the overlay for slow gUM is visible or not.
|
||||
*
|
||||
* {
|
||||
* type: TOGGLE_SLOW_GUM_OVERLAY,
|
||||
* isVisible: {boolean},
|
||||
* }
|
||||
* @public
|
||||
*/
|
||||
export const TOGGLE_SLOW_GUM_OVERLAY = 'TOGGLE_SLOW_GUM_OVERLAY';
|
||||
|
||||
/**
|
||||
* Adjust the state of the fatal error which shows/hides the reload screen. See
|
||||
* action methods's description for more info about each of the fields.
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
import {
|
||||
MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
SET_FATAL_ERROR,
|
||||
TOGGLE_SLOW_GUM_OVERLAY
|
||||
SET_FATAL_ERROR
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
@@ -27,24 +26,6 @@ export function mediaPermissionPromptVisibilityChanged(isVisible: boolean, brows
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the prompt for media permission is visible or not.
|
||||
*
|
||||
* @param {boolean} isVisible - If the value is true - the prompt for media
|
||||
* permission is visible otherwise the value is false/undefined.
|
||||
* @public
|
||||
* @returns {{
|
||||
* type: SLOW_GET_USER_MEDIA_OVERLAY,
|
||||
* isVisible: {boolean}
|
||||
* }}
|
||||
*/
|
||||
export function toggleSlowGUMOverlay(isVisible: boolean) {
|
||||
return {
|
||||
type: TOGGLE_SLOW_GUM_OVERLAY,
|
||||
isVisible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The action indicates that an unrecoverable error has occurred and the reload
|
||||
* screen will be displayed or hidden.
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link AbstractSlowGUMOverlay}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The function to translate human-readable text.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} for slow gUM overlay. Shown when
|
||||
* a slow gUM promise resolution is detected
|
||||
*/
|
||||
export default class AbstractSlowGUMOverlay extends Component<Props> {
|
||||
/**
|
||||
* Determines whether this overlay needs to be rendered (according to a
|
||||
* specific redux state). Called by {@link OverlayContainer}.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {boolean} - If this overlay needs to be rendered, {@code true};
|
||||
* {@code false}, otherwise.
|
||||
*/
|
||||
static needsRender(state: Object) {
|
||||
return state['features/overlay'].isSlowGUMOverlayVisible;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// @flow
|
||||
import Spinner from '@atlaskit/spinner';
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
import AbstractSlowGUMOverlay from './AbstractSlowGUMOverlay';
|
||||
import OverlayFrame from './OverlayFrame';
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} for slow gUM overlay. Shown when
|
||||
* a slow gUM promise resolution is detected
|
||||
*/
|
||||
class SlowGUMOverlay extends AbstractSlowGUMOverlay {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
// const { t } = this.props;
|
||||
|
||||
return (
|
||||
<OverlayFrame>
|
||||
<div className = { 'overlay__spinner-container' }>
|
||||
<Spinner
|
||||
invertColor = { true }
|
||||
size = { 'large' } />
|
||||
</div>
|
||||
</OverlayFrame>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(SlowGUMOverlay);
|
||||
@@ -5,4 +5,3 @@ export { default as OverlayFrame } from './OverlayFrame';
|
||||
export { default as PageReloadOverlay } from './PageReloadOverlay';
|
||||
export { default as SuspendedOverlay } from './SuspendedOverlay';
|
||||
export { default as UserMediaPermissionsOverlay } from './UserMediaPermissionsOverlay';
|
||||
export { default as SlowGUMOverlay } from './SlowGUMOverlay';
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import {
|
||||
PageReloadOverlay,
|
||||
SlowGUMOverlay,
|
||||
SuspendedOverlay,
|
||||
UserMediaPermissionsOverlay
|
||||
} from './components/web';
|
||||
@@ -18,7 +17,6 @@ export function getOverlays(): Array<Object> {
|
||||
return [
|
||||
PageReloadOverlay,
|
||||
SuspendedOverlay,
|
||||
UserMediaPermissionsOverlay,
|
||||
SlowGUMOverlay
|
||||
UserMediaPermissionsOverlay
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ import { assign, ReducerRegistry, set } from '../base/redux';
|
||||
|
||||
import {
|
||||
MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
SET_FATAL_ERROR,
|
||||
TOGGLE_SLOW_GUM_OVERLAY
|
||||
SET_FATAL_ERROR
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
@@ -29,8 +28,6 @@ ReducerRegistry.register('features/overlay', (state = { }, action) => {
|
||||
case SET_FATAL_ERROR:
|
||||
return _setFatalError(state, action);
|
||||
|
||||
case TOGGLE_SLOW_GUM_OVERLAY:
|
||||
return _toggleSlowGUMOverlay(state, action);
|
||||
}
|
||||
|
||||
return state;
|
||||
@@ -55,24 +52,6 @@ function _mediaPermissionPromptVisibilityChanged(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific redux action TOGGLE_SLOW_GUM_OVERLAY of
|
||||
* the feature overlay.
|
||||
*
|
||||
* @param {Object} state - The redux state of the feature overlay.
|
||||
* @param {Action} action - The redux action to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature overlay after the reduction of
|
||||
* the specified action.
|
||||
*/
|
||||
function _toggleSlowGUMOverlay(
|
||||
state,
|
||||
{ isVisible }) {
|
||||
return assign(state, {
|
||||
isSlowGUMOverlayVisible: isVisible
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code LoadConfigOverlay} overlay visible or not.
|
||||
*
|
||||
|
||||
@@ -4,6 +4,7 @@ declare var JitsiMeetJS: Object;
|
||||
|
||||
import uuid from 'uuid';
|
||||
|
||||
import { getRoomName } from '../base/conference';
|
||||
import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
|
||||
import { createLocalTrack } from '../base/lib-jitsi-meet';
|
||||
import {
|
||||
@@ -261,7 +262,10 @@ export function makePrecallTest(conferenceOptions: Object) {
|
||||
*/
|
||||
export function openDialInPage() {
|
||||
return function(dispatch: Function, getState: Function) {
|
||||
const dialInPage = getDialInfoPageURL(getState());
|
||||
const state = getState();
|
||||
const locationURL = state['features/base/connection'].locationURL;
|
||||
const roomName = getRoomName(state);
|
||||
const dialInPage = getDialInfoPageURL(roomName, locationURL);
|
||||
|
||||
openURLInBrowser(dialInPage, true);
|
||||
};
|
||||
|
||||
@@ -162,16 +162,3 @@ export function isPrejoinPageEnabled(state: Object): boolean {
|
||||
export function isPrejoinPageVisible(state: Object): boolean {
|
||||
return isPrejoinPageEnabled(state) && state['features/prejoin']?.showPrejoin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we should auto-knock in case lobby is enabled for the room.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function shouldAutoKnock(state: Object): boolean {
|
||||
const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
|
||||
|
||||
return (isPrejoinPageEnabled(state) || (iAmRecorder && iAmSipGateway))
|
||||
&& !state['features/lobby'].knocking;
|
||||
}
|
||||
|
||||
@@ -6,16 +6,10 @@ import {
|
||||
AUDIO_MUTE,
|
||||
createRemoteMuteConfirmedEvent,
|
||||
createToolbarEvent,
|
||||
sendAnalytics,
|
||||
VIDEO_MUTE
|
||||
sendAnalytics
|
||||
} from '../analytics';
|
||||
import { hideDialog } from '../base/dialog';
|
||||
import {
|
||||
MEDIA_TYPE,
|
||||
setAudioMuted,
|
||||
setVideoMuted,
|
||||
VIDEO_MUTISM_AUTHORITY
|
||||
} from '../base/media';
|
||||
import { setAudioMuted } from '../base/media';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
muteRemoteParticipant
|
||||
@@ -38,26 +32,17 @@ export function hideRemoteVideoMenu() {
|
||||
* Mutes the local participant.
|
||||
*
|
||||
* @param {boolean} enable - Whether to mute or unmute.
|
||||
* @param {MEDIA_TYPE} mediaType - The type of the media channel to mute.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function muteLocal(enable: boolean, mediaType: MEDIA_TYPE) {
|
||||
export function muteLocal(enable: boolean) {
|
||||
return (dispatch: Dispatch<any>) => {
|
||||
const isAudio = mediaType === MEDIA_TYPE.AUDIO;
|
||||
|
||||
if (!isAudio && mediaType !== MEDIA_TYPE.VIDEO) {
|
||||
console.error(`Unsupported media type: ${mediaType}`);
|
||||
|
||||
return;
|
||||
}
|
||||
sendAnalytics(createToolbarEvent(isAudio ? AUDIO_MUTE : VIDEO_MUTE, { enable }));
|
||||
dispatch(isAudio ? setAudioMuted(enable, /* ensureTrack */ true)
|
||||
: setVideoMuted(enable, mediaType, VIDEO_MUTISM_AUTHORITY.USER, /* ensureTrack */ true));
|
||||
sendAnalytics(createToolbarEvent(AUDIO_MUTE, { enable }));
|
||||
dispatch(setAudioMuted(enable, /* ensureTrack */ true));
|
||||
|
||||
// FIXME: The old conference logic as well as the shared video feature
|
||||
// still rely on this event being emitted.
|
||||
typeof APP === 'undefined'
|
||||
|| APP.UI.emitEvent(isAudio ? UIEvents.AUDIO_MUTED : UIEvents.VIDEO_MUTED, enable, true);
|
||||
|| APP.UI.emitEvent(UIEvents.AUDIO_MUTED, enable, true);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -65,18 +50,12 @@ export function muteLocal(enable: boolean, mediaType: MEDIA_TYPE) {
|
||||
* Mutes the remote participant with the given ID.
|
||||
*
|
||||
* @param {string} participantId - ID of the participant to mute.
|
||||
* @param {MEDIA_TYPE} mediaType - The type of the media channel to mute.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function muteRemote(participantId: string, mediaType: MEDIA_TYPE) {
|
||||
export function muteRemote(participantId: string) {
|
||||
return (dispatch: Dispatch<any>) => {
|
||||
if (mediaType !== MEDIA_TYPE.AUDIO && mediaType !== MEDIA_TYPE.VIDEO) {
|
||||
console.error(`Unsupported media type: ${mediaType}`);
|
||||
|
||||
return;
|
||||
}
|
||||
sendAnalytics(createRemoteMuteConfirmedEvent(participantId, mediaType));
|
||||
dispatch(muteRemoteParticipant(participantId, mediaType));
|
||||
sendAnalytics(createRemoteMuteConfirmedEvent(participantId));
|
||||
dispatch(muteRemoteParticipant(participantId));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,10 +63,9 @@ export function muteRemote(participantId: string, mediaType: MEDIA_TYPE) {
|
||||
* Mutes all participants.
|
||||
*
|
||||
* @param {Array<string>} exclude - Array of participant IDs to not mute.
|
||||
* @param {MEDIA_TYPE} mediaType - The media type to mute.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function muteAllParticipants(exclude: Array<string>, mediaType: MEDIA_TYPE) {
|
||||
export function muteAllParticipants(exclude: Array<string>) {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const state = getState();
|
||||
const localId = getLocalParticipant(state).id;
|
||||
@@ -97,7 +75,7 @@ export function muteAllParticipants(exclude: Array<string>, mediaType: MEDIA_TYP
|
||||
/* eslint-disable no-confusing-arrow */
|
||||
participantIds
|
||||
.filter(id => !exclude.includes(id))
|
||||
.map(id => id === localId ? muteLocal(true, mediaType) : muteRemote(id, mediaType))
|
||||
.map(id => id === localId ? muteLocal(true) : muteRemote(id))
|
||||
.map(dispatch);
|
||||
/* eslint-enable no-confusing-arrow */
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Dialog } from '../../base/dialog';
|
||||
import { MEDIA_TYPE } from '../../base/media';
|
||||
import { getLocalParticipant, getParticipantDisplayName } from '../../base/participants';
|
||||
import { muteAllParticipants } from '../actions';
|
||||
|
||||
@@ -70,7 +69,7 @@ export default class AbstractMuteEveryoneDialog<P: Props> extends AbstractMuteRe
|
||||
exclude
|
||||
} = this.props;
|
||||
|
||||
dispatch(muteAllParticipants(exclude, MEDIA_TYPE.AUDIO));
|
||||
dispatch(muteAllParticipants(exclude));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics';
|
||||
import { openDialog } from '../../base/dialog';
|
||||
import { IconMuteVideoEveryone } from '../../base/icons';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
|
||||
|
||||
import { MuteEveryonesVideoDialog } from '.';
|
||||
|
||||
export type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* The ID of the participant object that this button is supposed to keep unmuted.
|
||||
*/
|
||||
participantID: string,
|
||||
|
||||
/**
|
||||
* The function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* An abstract remote video menu button which disables the camera of all the other participants.
|
||||
*/
|
||||
export default class AbstractMuteEveryoneElsesVideoButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.muteEveryoneElsesVideo';
|
||||
icon = IconMuteVideoEveryone;
|
||||
label = 'videothumbnail.domuteVideoOfOthers';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens a confirmation dialog.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch, participantID } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('mute.everyoneelsesvideo.pressed'));
|
||||
dispatch(openDialog(MuteEveryonesVideoDialog, { exclude: [ participantID ] }));
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Dialog } from '../../base/dialog';
|
||||
import { MEDIA_TYPE } from '../../base/media';
|
||||
import { getLocalParticipant, getParticipantDisplayName } from '../../base/participants';
|
||||
import { muteAllParticipants } from '../actions';
|
||||
|
||||
import AbstractMuteRemoteParticipantsVideoDialog, {
|
||||
type Props as AbstractProps
|
||||
} from './AbstractMuteRemoteParticipantsVideoDialog';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link AbstractMuteEveryonesVideoDialog}.
|
||||
*/
|
||||
export type Props = AbstractProps & {
|
||||
|
||||
content: string,
|
||||
exclude: Array<string>,
|
||||
title: string
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* An abstract Component with the contents for a dialog that asks for confirmation
|
||||
* from the user before disabling all remote participants cameras.
|
||||
*
|
||||
* @extends AbstractMuteRemoteParticipantsVideoDialog
|
||||
*/
|
||||
export default class AbstractMuteEveryonesVideoDialog<P: Props> extends AbstractMuteRemoteParticipantsVideoDialog<P> {
|
||||
static defaultProps = {
|
||||
exclude: [],
|
||||
muteLocal: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { content, title } = this.props;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
okKey = 'dialog.muteParticipantsVideoButton'
|
||||
onSubmit = { this._onSubmit }
|
||||
titleString = { title }
|
||||
width = 'small'>
|
||||
<div>
|
||||
{ content }
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_onSubmit: () => boolean;
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the value of this dialog is submitted.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_onSubmit() {
|
||||
const {
|
||||
dispatch,
|
||||
exclude
|
||||
} = this.props;
|
||||
|
||||
dispatch(muteAllParticipants(exclude, MEDIA_TYPE.VIDEO));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated {@code AbstractMuteEveryonesVideoDialog}'s props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @param {Object} ownProps - The properties explicitly passed to the component.
|
||||
* @returns {Props}
|
||||
*/
|
||||
export function abstractMapStateToProps(state: Object, ownProps: Props) {
|
||||
const { exclude, t } = ownProps;
|
||||
|
||||
const whom = exclude
|
||||
// eslint-disable-next-line no-confusing-arrow
|
||||
.map(id => id === getLocalParticipant(state).id
|
||||
? t('dialog.muteEveryoneSelf')
|
||||
: getParticipantDisplayName(state, id))
|
||||
.join(', ');
|
||||
|
||||
return whom.length ? {
|
||||
content: t('dialog.muteEveryoneElsesVideoDialog'),
|
||||
title: t('dialog.muteEveryoneElsesVideoTitle', { whom })
|
||||
} : {
|
||||
content: t('dialog.muteEveryonesVideoDialog'),
|
||||
title: t('dialog.muteEveryonesVideoTitle')
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
import { MEDIA_TYPE } from '../../base/media';
|
||||
import { muteRemote } from '../actions';
|
||||
|
||||
/**
|
||||
@@ -58,7 +57,7 @@ export default class AbstractMuteRemoteParticipantDialog<P:Props = Props>
|
||||
_onSubmit() {
|
||||
const { dispatch, participantID } = this.props;
|
||||
|
||||
dispatch(muteRemote(participantID, MEDIA_TYPE.AUDIO));
|
||||
dispatch(muteRemote(participantID));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
import { MEDIA_TYPE } from '../../base/media';
|
||||
import { muteRemote } from '../actions';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link AbstractMuteRemoteParticipantsVideoDialog}.
|
||||
*/
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* The ID of the remote participant to be muted.
|
||||
*/
|
||||
participantID: string,
|
||||
|
||||
/**
|
||||
* Function to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract dialog to confirm a remote participant video ute action.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export default class AbstractMuteRemoteParticipantsVideoDialog<P:Props = Props>
|
||||
extends Component<P> {
|
||||
/**
|
||||
* Initializes a new {@code AbstractMuteRemoteParticipantsVideoDialog} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
}
|
||||
|
||||
_onSubmit: () => boolean;
|
||||
|
||||
/**
|
||||
* Handles the submit button action.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean} - True (to note that the modal should be closed).
|
||||
*/
|
||||
_onSubmit() {
|
||||
const { dispatch, participantID } = this.props;
|
||||
|
||||
dispatch(muteRemote(participantID, MEDIA_TYPE.VIDEO));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import {
|
||||
createRemoteVideoMenuButtonEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { openDialog } from '../../base/dialog';
|
||||
import { IconCameraDisabled } from '../../base/icons';
|
||||
import { MEDIA_TYPE } from '../../base/media';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
|
||||
import { isRemoteTrackMuted } from '../../base/tracks';
|
||||
|
||||
import { MuteRemoteParticipantsVideoDialog } from '.';
|
||||
|
||||
export type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* Boolean to indicate if the video track of the participant is muted or
|
||||
* not.
|
||||
*/
|
||||
_videoTrackMuted: boolean,
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* The ID of the participant object that this button is supposed to
|
||||
* mute/unmute.
|
||||
*/
|
||||
participantID: string,
|
||||
|
||||
/**
|
||||
* The function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* An abstract remote video menu button which mutes the remote participant.
|
||||
*/
|
||||
export default class AbstractMuteVideoButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.remoteVideoMute';
|
||||
icon = IconCameraDisabled;
|
||||
label = 'videothumbnail.domuteVideo';
|
||||
toggledLabel = 'videothumbnail.videoMuted';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and mutes the participant.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch, participantID } = this.props;
|
||||
|
||||
sendAnalytics(createRemoteVideoMenuButtonEvent(
|
||||
'mute.button',
|
||||
{
|
||||
'participant_id': participantID
|
||||
}));
|
||||
|
||||
dispatch(openDialog(MuteRemoteParticipantsVideoDialog, { participantID }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the item disabled if the participant is muted.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_isDisabled() {
|
||||
return this.props._videoTrackMuted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the item toggled if the participant is muted.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_isToggled() {
|
||||
return this.props._videoTrackMuted;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that maps parts of Redux state tree into component props.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @param {Object} ownProps - Properties of component.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _videoTrackMuted: boolean
|
||||
* }}
|
||||
*/
|
||||
export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
const tracks = state['features/base/tracks'];
|
||||
|
||||
return {
|
||||
_videoTrackMuted: isRemoteTrackMuted(
|
||||
tracks, MEDIA_TYPE.VIDEO, ownProps.participantID)
|
||||
};
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconMuteVideoEveryoneElse } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import AbstractMuteEveryoneElsesVideoButton, {
|
||||
type Props
|
||||
} from '../AbstractMuteEveryoneElsesVideoButton';
|
||||
|
||||
import RemoteVideoMenuButton from './RemoteVideoMenuButton';
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays a button for audio muting
|
||||
* every participant in the conference except the one with the given
|
||||
* participantID
|
||||
*/
|
||||
class MuteEveryoneElsesVideoButton extends AbstractMuteEveryoneElsesVideoButton {
|
||||
/**
|
||||
* Instantiates a new {@code Component}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._handleClick = this._handleClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { participantID, t } = this.props;
|
||||
|
||||
return (
|
||||
<RemoteVideoMenuButton
|
||||
buttonText = { t('videothumbnail.domuteVideoOfOthers') }
|
||||
displayClass = { 'mutelink' }
|
||||
icon = { IconMuteVideoEveryoneElse }
|
||||
id = { `mutelink_${participantID}` }
|
||||
// eslint-disable-next-line react/jsx-handler-names
|
||||
onClick = { this._handleClick } />
|
||||
);
|
||||
}
|
||||
|
||||
_handleClick: () => void;
|
||||
}
|
||||
|
||||
export default translate(connect()(MuteEveryoneElsesVideoButton));
|
||||
@@ -1,41 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Dialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import AbstractMuteEveryonesVideoDialog, { abstractMapStateToProps, type Props }
|
||||
from '../AbstractMuteEveryonesVideoDialog';
|
||||
|
||||
/**
|
||||
* A React Component with the contents for a dialog that asks for confirmation
|
||||
* from the user before disabling all remote participants cameras.
|
||||
*
|
||||
* @extends AbstractMuteEveryonesVideoDialog
|
||||
*/
|
||||
class MuteEveryonesVideoDialog extends AbstractMuteEveryonesVideoDialog<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<Dialog
|
||||
okKey = 'dialog.muteParticipantsVideoButton'
|
||||
onSubmit = { this._onSubmit }
|
||||
titleString = { this.props.title }
|
||||
width = 'small'>
|
||||
<div>
|
||||
{ this.props.content }
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_onSubmit: () => boolean;
|
||||
}
|
||||
|
||||
export default translate(connect(abstractMapStateToProps)(MuteEveryonesVideoDialog));
|
||||
@@ -1,41 +0,0 @@
|
||||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Dialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import AbstractMuteRemoteParticipantsVideoDialog
|
||||
from '../AbstractMuteRemoteParticipantsVideoDialog';
|
||||
|
||||
/**
|
||||
* A React Component with the contents for a dialog that asks for confirmation
|
||||
* from the user before disabling a remote participants camera.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class MuteRemoteParticipantsVideoDialog extends AbstractMuteRemoteParticipantsVideoDialog {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<Dialog
|
||||
okKey = 'dialog.muteParticipantsVideoButton'
|
||||
onSubmit = { this._onSubmit }
|
||||
titleKey = 'dialog.muteParticipantsVideoTitle'
|
||||
width = 'small'>
|
||||
<div>
|
||||
{ this.props.t('dialog.muteParticipantsVideoBody') }
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_onSubmit: () => boolean;
|
||||
}
|
||||
|
||||
export default translate(connect()(MuteRemoteParticipantsVideoDialog));
|
||||
@@ -1,67 +0,0 @@
|
||||
/* @flow */
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconCameraDisabled } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import AbstractMuteVideoButton, {
|
||||
_mapStateToProps,
|
||||
type Props
|
||||
} from '../AbstractMuteVideoButton';
|
||||
|
||||
import RemoteVideoMenuButton from './RemoteVideoMenuButton';
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays a button for disabling
|
||||
* the camera of a participant in the conference.
|
||||
*
|
||||
* NOTE: At the time of writing this is a button that doesn't use the
|
||||
* {@code AbstractButton} base component, but is inherited from the same
|
||||
* super class ({@code AbstractMuteVideoButton} that extends {@code AbstractButton})
|
||||
* for the sake of code sharing between web and mobile. Once web uses the
|
||||
* {@code AbstractButton} base component, this can be fully removed.
|
||||
*/
|
||||
class MuteVideoButton extends AbstractMuteVideoButton {
|
||||
/**
|
||||
* Instantiates a new {@code Component}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._handleClick = this._handleClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _videoTrackMuted, participantID, t } = this.props;
|
||||
const muteConfig = _videoTrackMuted ? {
|
||||
translationKey: 'videothumbnail.videoMuted',
|
||||
muteClassName: 'mutelink disabled'
|
||||
} : {
|
||||
translationKey: 'videothumbnail.domuteVideo',
|
||||
muteClassName: 'mutelink'
|
||||
};
|
||||
|
||||
return (
|
||||
<RemoteVideoMenuButton
|
||||
buttonText = { t(muteConfig.translationKey) }
|
||||
displayClass = { muteConfig.muteClassName }
|
||||
icon = { IconCameraDisabled }
|
||||
id = { `mutelink_${participantID}` }
|
||||
// eslint-disable-next-line react/jsx-handler-names
|
||||
onClick = { this._handleClick } />
|
||||
);
|
||||
}
|
||||
|
||||
_handleClick: () => void
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(MuteVideoButton));
|
||||
@@ -3,20 +3,20 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Icon, IconMenuThumb } from '../../../base/icons';
|
||||
import { MEDIA_TYPE } from '../../../base/media';
|
||||
import { getLocalParticipant, getParticipantById, PARTICIPANT_ROLE } from '../../../base/participants';
|
||||
import { Popover } from '../../../base/popover';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { isRemoteTrackMuted } from '../../../base/tracks';
|
||||
import { requestRemoteControl, stopController } from '../../../remote-control';
|
||||
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
|
||||
|
||||
import MuteEveryoneElseButton from './MuteEveryoneElseButton';
|
||||
import MuteEveryoneElsesVideoButton from './MuteEveryoneElsesVideoButton';
|
||||
import { REMOTE_CONTROL_MENU_STATES } from './RemoteControlButton';
|
||||
|
||||
import {
|
||||
GrantModeratorButton,
|
||||
MuteButton,
|
||||
MuteVideoButton,
|
||||
KickButton,
|
||||
PrivateMessageMenuButton,
|
||||
RemoteControlButton,
|
||||
@@ -43,6 +43,11 @@ type Props = {
|
||||
*/
|
||||
_disableRemoteMute: Boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the participant is currently muted.
|
||||
*/
|
||||
_isAudioMuted: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the participant is a conference moderator.
|
||||
*/
|
||||
@@ -146,6 +151,7 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||
const {
|
||||
_disableKick,
|
||||
_disableRemoteMute,
|
||||
_isAudioMuted,
|
||||
_isModerator,
|
||||
dispatch,
|
||||
initialVolumeValue,
|
||||
@@ -160,6 +166,7 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||
if (!_disableRemoteMute) {
|
||||
buttons.push(
|
||||
<MuteButton
|
||||
isAudioMuted = { _isAudioMuted }
|
||||
key = 'mute'
|
||||
participantID = { participantID } />
|
||||
);
|
||||
@@ -168,16 +175,6 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||
key = 'mute-others'
|
||||
participantID = { participantID } />
|
||||
);
|
||||
buttons.push(
|
||||
<MuteVideoButton
|
||||
key = 'mute-video'
|
||||
participantID = { participantID } />
|
||||
);
|
||||
buttons.push(
|
||||
<MuteEveryoneElsesVideoButton
|
||||
key = 'mute-others-video'
|
||||
participantID = { participantID } />
|
||||
);
|
||||
}
|
||||
|
||||
buttons.push(
|
||||
@@ -250,6 +247,7 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
const { participantID } = ownProps;
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const { remoteVideoMenu = {}, disableRemoteMute } = state['features/base/config'];
|
||||
const { disableKick } = remoteVideoMenu;
|
||||
@@ -288,6 +286,7 @@ function _mapStateToProps(state, ownProps) {
|
||||
}
|
||||
|
||||
return {
|
||||
_isAudioMuted: isRemoteTrackMuted(tracks, MEDIA_TYPE.AUDIO, participantID) || false,
|
||||
_isModerator: Boolean(localParticipant?.role === PARTICIPANT_ROLE.MODERATOR),
|
||||
_disableKick: Boolean(disableKick),
|
||||
_disableRemoteMute: Boolean(disableRemoteMute),
|
||||
|
||||
@@ -5,13 +5,9 @@ export { default as GrantModeratorDialog } from './GrantModeratorDialog';
|
||||
export { default as KickButton } from './KickButton';
|
||||
export { default as KickRemoteParticipantDialog } from './KickRemoteParticipantDialog';
|
||||
export { default as MuteButton } from './MuteButton';
|
||||
export { default as MuteVideoButton } from './MuteVideoButton';
|
||||
export { default as MuteEveryoneDialog } from './MuteEveryoneDialog';
|
||||
export { default as MuteEveryonesVideoDialog } from './MuteEveryonesVideoDialog';
|
||||
export { default as MuteEveryoneElseButton } from './MuteEveryoneElseButton';
|
||||
export { default as MuteEveryoneElsesVideoButton } from './MuteEveryoneElsesVideoButton';
|
||||
export { default as MuteRemoteParticipantDialog } from './MuteRemoteParticipantDialog';
|
||||
export { default as MuteRemoteParticipantsVideoDialog } from './MuteRemoteParticipantsVideoDialog';
|
||||
export { default as PrivateMessageMenuButton } from './PrivateMessageMenuButton';
|
||||
export { REMOTE_CONTROL_MENU_STATES, default as RemoteControlButton } from './RemoteControlButton';
|
||||
export { default as RemoteVideoMenu } from './RemoteVideoMenu';
|
||||
|
||||
@@ -6,6 +6,9 @@ import {
|
||||
timerWorkerScript
|
||||
} from './TimerWorker';
|
||||
|
||||
const segmentationWidth = 256;
|
||||
const segmentationHeight = 144;
|
||||
const segmentationPixelCount = segmentationWidth * segmentationHeight;
|
||||
const blurValue = '25px';
|
||||
|
||||
/**
|
||||
@@ -15,8 +18,6 @@ const blurValue = '25px';
|
||||
*/
|
||||
export default class JitsiStreamBlurEffect {
|
||||
_model: Object;
|
||||
_options: Object;
|
||||
_segmentationPixelCount: number;
|
||||
_inputVideoElement: HTMLVideoElement;
|
||||
_onMaskFrameTimer: Function;
|
||||
_maskFrameTimerWorker: Worker;
|
||||
@@ -34,13 +35,10 @@ export default class JitsiStreamBlurEffect {
|
||||
* Represents a modified video MediaStream track.
|
||||
*
|
||||
* @class
|
||||
* @param {Object} model - Meet model.
|
||||
* @param {Object} options - Segmentation dimensions.
|
||||
* @param {BodyPix} bpModel - BodyPix model.
|
||||
*/
|
||||
constructor(model: Object, options: Object) {
|
||||
this._model = model;
|
||||
this._options = options;
|
||||
this._segmentationPixelCount = this._options.width * this._options.height;
|
||||
constructor(bpModel: Object) {
|
||||
this._model = bpModel;
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onMaskFrameTimer = this._onMaskFrameTimer.bind(this);
|
||||
@@ -78,8 +76,8 @@ export default class JitsiStreamBlurEffect {
|
||||
this._segmentationMaskCanvas,
|
||||
0,
|
||||
0,
|
||||
this._options.width,
|
||||
this._options.height,
|
||||
segmentationWidth,
|
||||
segmentationHeight,
|
||||
0,
|
||||
0,
|
||||
this._inputVideoElement.width,
|
||||
@@ -91,7 +89,7 @@ export default class JitsiStreamBlurEffect {
|
||||
this._outputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
|
||||
|
||||
this._outputCanvasCtx.globalCompositeOperation = 'destination-over';
|
||||
this._outputCanvasCtx.filter = `blur(${blurValue})`;
|
||||
this._outputCanvasCtx.filter = `blur(${blurValue})`; // FIXME Does not work on Safari.
|
||||
this._outputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
|
||||
}
|
||||
|
||||
@@ -104,7 +102,7 @@ export default class JitsiStreamBlurEffect {
|
||||
this._model._runInference();
|
||||
const outputMemoryOffset = this._model._getOutputMemoryOffset() / 4;
|
||||
|
||||
for (let i = 0; i < this._segmentationPixelCount; i++) {
|
||||
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);
|
||||
@@ -148,19 +146,19 @@ export default class JitsiStreamBlurEffect {
|
||||
this._inputVideoElement.height,
|
||||
0,
|
||||
0,
|
||||
this._options.width,
|
||||
this._options.height
|
||||
segmentationWidth,
|
||||
segmentationHeight
|
||||
);
|
||||
|
||||
const imageData = this._segmentationMaskCtx.getImageData(
|
||||
0,
|
||||
0,
|
||||
this._options.width,
|
||||
this._options.height
|
||||
segmentationWidth,
|
||||
segmentationHeight
|
||||
);
|
||||
const inputMemoryOffset = this._model._getInputMemoryOffset() / 4;
|
||||
|
||||
for (let i = 0; i < this._segmentationPixelCount; i++) {
|
||||
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;
|
||||
@@ -191,10 +189,10 @@ export default class JitsiStreamBlurEffect {
|
||||
const { height, frameRate, width }
|
||||
= firstVideoTrack.getSettings ? firstVideoTrack.getSettings() : firstVideoTrack.getConstraints();
|
||||
|
||||
this._segmentationMask = new ImageData(this._options.width, this._options.height);
|
||||
this._segmentationMask = new ImageData(segmentationWidth, segmentationHeight);
|
||||
this._segmentationMaskCanvas = document.createElement('canvas');
|
||||
this._segmentationMaskCanvas.width = this._options.width;
|
||||
this._segmentationMaskCanvas.height = this._options.height;
|
||||
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);
|
||||
|
||||
@@ -7,19 +7,8 @@ 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
|
||||
}
|
||||
'96': 'libs/segm_lite_v681.tflite',
|
||||
'144': 'libs/segm_full_v679.tflite'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -42,20 +31,14 @@ export async function createBlurEffect() {
|
||||
|
||||
const modelBufferOffset = tflite._getModelBufferMemoryOffset();
|
||||
const modelResponse = await fetch(
|
||||
wasmCheck.feature.simd ? models.model144 : models.model96
|
||||
models['144']
|
||||
);
|
||||
|
||||
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);
|
||||
return new JitsiStreamBlurEffect(tflite);
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ class AudioMuteButton extends AbstractAudioMuteButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_setAudioMuted(audioMuted: boolean) {
|
||||
this.props.dispatch(muteLocal(audioMuted, MEDIA_TYPE.AUDIO));
|
||||
this.props.dispatch(muteLocal(audioMuted));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics';
|
||||
import { openDialog } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { IconMuteVideoEveryone } from '../../base/icons';
|
||||
import { getLocalParticipant, PARTICIPANT_ROLE } from '../../base/participants';
|
||||
import { connect } from '../../base/redux';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
|
||||
import { MuteEveryonesVideoDialog } from '../../remote-video-menu/components';
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/*
|
||||
** Whether the local participant is a moderator or not.
|
||||
*/
|
||||
isModerator: Boolean,
|
||||
|
||||
/**
|
||||
* The ID of the local participant.
|
||||
*/
|
||||
localParticipantId: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays a button for disabling the camera of
|
||||
* every participant (except the local one)
|
||||
*/
|
||||
class MuteEveryonesVideoButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.muteEveryonesVideo';
|
||||
icon = IconMuteVideoEveryone;
|
||||
label = 'toolbar.muteEveryonesVideo';
|
||||
tooltip = 'toolbar.muteVideoEveryone';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens a confirmation dialog.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch, localParticipantId } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('mute.everyone.pressed'));
|
||||
dispatch(openDialog(MuteEveryonesVideoDialog, {
|
||||
exclude: [ localParticipantId ]
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the redux state to the component's props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @param {Props} ownProps - The component's own props.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const isModerator = localParticipant.role === PARTICIPANT_ROLE.MODERATOR;
|
||||
const { visible } = ownProps;
|
||||
const { disableRemoteMute } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
isModerator,
|
||||
localParticipantId: localParticipant.id,
|
||||
visible: visible && isModerator && !disableRemoteMute
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(MuteEveryonesVideoButton));
|
||||
@@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import * as wasmCheck from 'wasm-check';
|
||||
|
||||
import {
|
||||
ACTION_SHORTCUT_TRIGGERED,
|
||||
@@ -36,7 +37,6 @@ 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';
|
||||
@@ -82,7 +82,6 @@ import DownloadButton from '../DownloadButton';
|
||||
import HangupButton from '../HangupButton';
|
||||
import HelpButton from '../HelpButton';
|
||||
import MuteEveryoneButton from '../MuteEveryoneButton';
|
||||
import MuteEveryonesVideoButton from '../MuteEveryonesVideoButton';
|
||||
|
||||
import AudioSettingsButton from './AudioSettingsButton';
|
||||
import OverflowMenuButton from './OverflowMenuButton';
|
||||
@@ -1071,7 +1070,7 @@ class Toolbox extends Component<Props, State> {
|
||||
&& <VideoBlurButton
|
||||
key = 'videobackgroundblur'
|
||||
showLabel = { true }
|
||||
visible = { !_screensharing && checkBlurSupport() } />,
|
||||
visible = { !_screensharing && wasmCheck.feature.simd } />,
|
||||
this._shouldShowButton('settings')
|
||||
&& <SettingsButton
|
||||
key = 'settings'
|
||||
@@ -1080,10 +1079,6 @@ class Toolbox extends Component<Props, State> {
|
||||
&& <MuteEveryoneButton
|
||||
key = 'mute-everyone'
|
||||
showLabel = { true } />,
|
||||
this._shouldShowButton('mute-video-everyone')
|
||||
&& <MuteEveryonesVideoButton
|
||||
key = 'mute-video-everyone'
|
||||
showLabel = { true } />,
|
||||
this._shouldShowButton('stats')
|
||||
&& <OverflowMenuItem
|
||||
accessibilityLabel = { t('toolbar.accessibilityLabel.speakerStats') }
|
||||
|
||||
@@ -216,7 +216,7 @@ function onConnectionSuccess() {
|
||||
devices.push('video');
|
||||
}
|
||||
|
||||
if (localAudio || autoCreateLocalAudio) {
|
||||
if (autoCreateLocalAudio) {
|
||||
devices.push('audio');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user