Compare commits

...

9 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
500b326780 ios: fix not activating Audio Unit in some corner cases
When a user was in a conference, and then goes bacck to being alone the Audio
Unit may not work properly if this state lasted for too long (around the 10
minute mark).

The solution this commit implements is based on the use of manual audio, that
is, WebRTC will no longer activate / deactivate the audio unit by itself, we
have to. Thus the Audio Unit will not be enabled until there are at least 2
participants in the meeting.

This is not enough, however, since CallKit will put the AVAudioSession in a
weird state (setActive will jusst fail) after about 10 minutes, so we have to
add another workaround: "hold" the call in CallKit while we are alone in a
meeting.
2020-05-12 18:35:31 +02:00
bgrozev
38b8772af0 docs: Fix video tutorial links. (#6655) 2020-05-11 11:23:35 -05:00
Mihai Uscat
5fdb7c176c feat(MobileDeepLinking): Enable custom deeplinking domain 2020-05-11 15:33:10 +02:00
Saúl Ibarra Corretgé
a8da6d4095 lang: improve Basque translation
Thanks @irontec, @librezale and @arraintxo for working on it!
2020-05-11 15:32:31 +02:00
Saúl Ibarra Corretgé
01fc098d4b prejoin: fixup mobile
Avoid importing components, which are not yet implemented on mobile.
2020-05-11 14:15:55 +02:00
Saúl Ibarra Corretgé
b50b30e3eb doc: recommend Node 12 2020-05-11 13:51:00 +02:00
Wuriyanto
1b8e5d0244 change cjson to cjson.safe and cath error from decode function 2020-05-11 05:46:07 -05:00
Vlad Piersec
908712b96f feat(prejoin_page): Rework prejoin page
* Add a checkbox for skipping the prejoin page on next use. (This is hidden for
now, until we also have a settings entry for it).
* Rework 'Join by Phone' buttons and add new overlay.
* Update the device status accordingly if there were errors while adding
devices.
* The input is filled with the display name if there was one previously used.
* Join the meeting on 'Enter' press.
2020-05-11 10:59:52 +02:00
Goacid
a41bda73ff doc: add more details about systemd limits 2020-05-11 09:33:50 +02:00
34 changed files with 770 additions and 317 deletions

View File

@@ -9,9 +9,9 @@
&-input-area-container {
position: absolute;
bottom: 128px;
bottom: 48px;
width: 100%;
z-index: 1;
z-index: 2;
}
&-input-area {
@@ -34,8 +34,8 @@
display: inline-block;
font-size: 15px;
line-height: 24px;
margin-bottom: 16px;
padding: 7px 16px;
position: relative;
text-align: center;
width: 286px;
@@ -51,11 +51,24 @@
&--text {
width: auto;
font-size: 13px;
margin: 0;
padding: 0;
}
}
&-btn-options {
align-items: center;
border-left: 1px solid #fff;
display: flex;
height: 100%;
justify-content: center;
position: absolute;
right: 0;
top: 0;
width: 40px;
}
&-text-btns {
display: flex;
justify-content: space-between;
@@ -69,6 +82,25 @@
text-align: center;
width: 100%;
}
&-checkbox {
border: 0;
height: 16px;
margin-right: 8px;
padding: 0;
width: 16px;
}
&-checkbox-container {
align-items: center;
color: #fff;
display: none;
font-size: 13px;
justify-content: center;
line-height: 20px;
margin-top: 16px;
width: 100%;
}
}
@mixin name-placeholder {
@@ -128,10 +160,8 @@
&-btn-container {
display: flex;
justify-content: center;
position: absolute;
bottom: 50px;
margin-top: 32px;
width: 100%;
z-index: 1;
&> div {
margin: 0 12px;
@@ -151,7 +181,16 @@
position: absolute;
width: 100%;
z-index: 1;
background: linear-gradient(0deg, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), linear-gradient(360deg, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0) 54.25%);
background: linear-gradient(0deg, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3));
}
&-bottom-overlay {
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.9) 100%);
bottom: 0;
height: 50%;
position: absolute;
width: 100%;
z-index: 1;
}
&-status {
@@ -192,6 +231,43 @@
width: 49px;
margin: 0 8px;
}
&-dropdown-btns {
width: 320px;
padding: 8px 0;
}
&-dropdown-btn {
align-items: center;
color: #1C2025;
cursor: pointer;
display: flex;
height: 40px;
font-size: 15px;
line-height: 24px;
padding: 0 16px;
&:hover {
background-color: #DAEBFA;
}
}
&-dropdown-icon {
display: inline-block;
margin-right: 16px;
& > svg {
fill: #1C2025;
}
}
&-dropdown-container {
& > div > div:nth-child(2) > div > div {
background: #fff;
padding: 0;
}
}
}
.prejoin-copy {

View File

@@ -16,7 +16,7 @@ or anyone who wishes to deploy and operate their own Jitsi Meet instance.
Work in progress.
## Developer guide
## Developer guides
### Web
@@ -30,12 +30,14 @@ Work in progress.
* [Enabling Dropbox support](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile-dropbox.md)
* [Enabling Google authentication](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile-google-auth.md)
## DevOps guide
## DevOps guides
* [Quick install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md)
* [Docker install](https://github.com/jitsi/docker-jitsi-meet/blob/master/README.md)
* [Google Calendar, MS Calendar, Dropbox integrations](https://github.com/jitsi/jitsi-meet/blob/master/doc/integrations.md)
* [Video tutorials on deployment and scalability](https://jitsi.org/tutorials/)
* [Video: Installing Jitsi Meet on your own Linux Server](https://jitsi.org/news/new-tutorial-installing-jitsi-meet-on-your-own-linux-server/)
* [Video: How to Load Balance Jitsi Meet](https://jitsi.org/blog/tutorial-video-how-to-load-balance-jitsi-meet/)
* [Video: Scaling Jitsi Meet in the Cloud](https://jitsi.org/blog/new-tutorial-video-scaling-jitsi-meet-in-the-cloud/)
* [Configuring a video SIP gateway](https://github.com/jitsi/jitsi-meet/blob/master/doc/sipgw-config.md)
* [Enabling speaker stats](https://github.com/jitsi/jitsi-meet/blob/master/doc/speakerstats-prosody.md)
* [Enabling TURN](https://github.com/jitsi/jitsi-meet/blob/master/doc/turn.md)

View File

@@ -2,7 +2,7 @@
## Building the sources
Node.js >= 10 and npm >= 6 are required.
Node.js >= 12 and npm >= 6 are required.
On Debian/Ubuntu systems, the required packages can be installed with:
```

View File

@@ -227,7 +227,7 @@ npm install
make
```
_NOTE: When installing on older distributions keep in mind that you need Node.js >= 10 and npm >= 6._
_NOTE: When installing on older distributions keep in mind that you need Node.js >= 12 and npm >= 6._
Edit host names in `/srv/jitsi-meet/config.js` (see also the example config file):
```

View File

@@ -81,12 +81,18 @@ See [the documentation of ice4j](https://github.com/jitsi/ice4j/blob/master/doc/
for details.
Default deployments on systems using systemd will have low default values for maximum processes and open files. If the used bridge will expect higher number of participants the default values need to be adjusted (the default values are good for less than 100 participants).
To update the values edit `/etc/systemd/system.conf` and make sure you have the following values:
To update the values edit `/etc/systemd/system.conf` and make sure you have the following values if values are smaller, if not do not update.
```
DefaultLimitNOFILE=65000
DefaultLimitNPROC=65000
DefaultTasksMax=65000
```
To check values just run :
```
systemctl show --property DefaultLimitNPROC
systemctl show --property DefaultLimitNOFILE
systemctl show --property DefaultTasksMax
```
To load the values and check them look [here](#systemd-details) for details.
By default, anyone who has access to your jitsi instance will be able to start a conference: if your server is open to the world, anyone can have a chat with anyone else. If you want to limit the ability to start a conference to registered users, set up a "secure domain". Follow the instructions at https://github.com/jitsi/jicofo#secure-domain.

View File

@@ -230,6 +230,17 @@ var interfaceConfig = {
*/
// MOBILE_DOWNLOAD_LINK_IOS: 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905',
/**
* Specify Firebase dynamic link properties for the mobile apps.
*/
// MOBILE_DYNAMIC_LINK: {
// APN: 'org.jitsi.meet',
// APP_CODE: 'w2atb',
// CUSTOM_DOMAIN: undefined,
// IBI: 'com.atlassian.JitsiMeet.ios',
// ISI: '1165103905'
// },
/**
* Specify mobile app scheme for opening the app from the mobile browser.
*/

View File

@@ -115,6 +115,9 @@ RCT_EXPORT_MODULE();
RTCAudioSession *session = [RTCAudioSession sharedInstance];
[session addDelegate:self];
// Don't let WebRTC manage the audio unit, we'll do it by hand.
session.useManualAudio = YES;
}
return self;
@@ -155,6 +158,8 @@ RCT_EXPORT_METHOD(setMode:(int)mode
forceEarpiece = NO;
}
DDLogInfo(@"[AudioMode] setMode: %d", mode);
activeMode = mode;
if ([self setConfig:config error:&error]) {
@@ -230,6 +235,47 @@ RCT_EXPORT_METHOD(setAudioDevice:(NSString *)device
}
}
RCT_EXPORT_METHOD(setAudioEnabled:(BOOL)enabled) {
DDLogInfo(@"[AudioMode] setAudioEnabled");
RTCAudioSession *session = [RTCAudioSession sharedInstance];
if (enabled) {
// CallKit should have activated the session, but it's possible it got deeactivated when
// a user who was in a conferene is left alone for a long period of time (~10 minutes).
if (!session.isActive) {
DDLogInfo(@"[AudioMode][setAudioEnabled] Session is not active, activating...");
// Pretend an interruption ended so RTCAudioSession updates its own internal state.
// It will re-activate by itself. We need to do this because otherwisse there is no
// way to get rid of an internal `isInterrupted` flag.
NSDictionary *userInfo
= @{
AVAudioSessionInterruptionTypeKey: [NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded],
AVAudioSessionInterruptionOptionKey: [NSNumber numberWithInt:AVAudioSessionInterruptionOptionShouldResume]
};
[[NSNotificationCenter defaultCenter] postNotificationName:AVAudioSessionInterruptionNotification object:nil userInfo:userInfo];
RTCAudioSessionConfiguration *config = [self configForMode:activeMode];
NSError *error = nil;
[self setConfig:config error:&error];
if (error != nil) {
DDLogError(@"[AudioMode][setAudioEnabled] setConfig: %@", error);
}
}
// Make sure the Audio Unit is started. This is idempotent, so we don't need to worry about always setting it.
DDLogInfo(@"[AudioMode][setAudioEnabled] Enabling Audio Unit, current sttate: %d", session.isAudioEnabled);
session.isAudioEnabled = YES;
} else {
DDLogInfo(@"[AudioMode][setAudioEnabled] Disabling Audio Unit, current sttate: %d", session.isAudioEnabled);
session.isAudioEnabled = NO;
// Ignore the AVAudioSession activation state. CallKit or the system will disable it.
}
}
RCT_EXPORT_METHOD(updateDeviceList) {
[self notifyDevicesChanged];
}

View File

@@ -38,6 +38,8 @@ static NSString * const RNCallKitPerformAnswerCallAction
= @"performAnswerCallAction";
static NSString * const RNCallKitPerformEndCallAction
= @"performEndCallAction";
static NSString * const RNCallKitPerformSetHeldCallAction
= @"performSetHeldCallAction";
static NSString * const RNCallKitPerformSetMutedCallAction
= @"performSetMutedCallAction";
static NSString * const RNCallKitProviderDidReset
@@ -54,6 +56,7 @@ RCT_EXPORT_MODULE();
return @[
RNCallKitPerformAnswerCallAction,
RNCallKitPerformEndCallAction,
RNCallKitPerformSetHeldCallAction,
RNCallKitPerformSetMutedCallAction,
RNCallKitProviderDidReset
];
@@ -120,6 +123,25 @@ RCT_EXPORT_METHOD(setProviderConfiguration:(NSDictionary *)dictionary) {
[JMCallKitProxy addListener:self];
}
RCT_EXPORT_METHOD(setCallOnHold:(NSString *)callUUID
onHold:(BOOL)onHold
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
DDLogInfo(@"[RNCallKit][startCall] setCallOnHold = %@", callUUID);
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
if (!callUUID_) {
reject(nil, [NSString stringWithFormat:@"Invalid UUID: %@", callUUID], nil);
return;
}
CXSetHeldCallAction *action
= [[CXSetHeldCallAction alloc] initWithCallUUID:callUUID_ onHold:onHold];
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:action];
[self requestTransaction:transaction resolve:resolve reject:reject];
}
// Start outgoing call
RCT_EXPORT_METHOD(startCall:(NSString *)callUUID
handle:(NSString *)handle
@@ -295,6 +317,18 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
body:@{ @"callUUID": UUID.UUIDString }];
}
// Handle holdd from CallKit view
- (void) performSetHeldCallWithUUID:(NSUUID *)UUID
isOnHold:(BOOL)isOnHold {
DDLogInfo(@"[RNCallKit][CXProviderDelegate][provider:performSetHeldCallAction:]");
[self sendEventWithName:RNCallKitPerformSetHeldCallAction
body:@{
@"callUUID": UUID.UUIDString,
@"onHold": @(isOnHold)
}];
}
// Handle audio mute from CallKit view
- (void) performSetMutedCallWithUUID:(NSUUID *)UUID
isMuted:(BOOL)isMuted {

View File

@@ -71,6 +71,15 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
listeners.forEach {
let listener = $0 as! JMCallKitListener
listener.performSetHeldCall?(UUID: action.callUUID, isOnHold: action.isOnHold)
}
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
let uuid = pendingMuteActions.remove(action.uuid)

View File

@@ -26,6 +26,8 @@ import Foundation
@objc optional func performEndCall(UUID: UUID)
@objc optional func performSetHeldCall(UUID: UUID, isOnHold: Bool)
@objc optional func performSetMutedCall(UUID: UUID, isMuted: Bool)
@objc optional func performStartCall(UUID: UUID, isVideo: Bool)

View File

@@ -2,7 +2,7 @@
"addPeople": {
"add": "Gonbidatu",
"countryNotSupported": "Oraindik ez dugu helmuga hau onartzen.",
"countryReminder": "AEBetatik kanpo deitzen? Ziurtatu herrialde-kodearekin hasten zarela!",
"countryReminder": "Atzerritik deitzen? Ziurtatu herrialde-kodearekin hasten zarela!",
"disabled": "Ezin duzu jendea gonbidatu.",
"failedToAdd": "Parte-hartzaileak gehitzeak huts egin du",
"footerText": "Markatzea desgaituta dago.",
@@ -175,6 +175,10 @@
"dismiss": "Baztertu",
"displayNameRequired": "Kaixo! Zein da zure izena?",
"done": "Eginda",
"e2eeDescription": "",
"e2eeLabel": "",
"e2eeTitle": "",
"e2eeWarning": "",
"enterDisplayName": "Sartu zure izena hemen",
"error": "Errorea",
"externalInstallationMsg": "Mahaigaineko partekatze-luzapena instalatu behar duzu.",
@@ -290,6 +294,9 @@
"documentSharing": {
"title": "Partekatutako dokumentua"
},
"e2ee": {
"labelToolTip": ""
},
"feedback": {
"average": "Normala",
"bad": "Txarra",
@@ -316,11 +323,11 @@
"dialInConferenceID": "PIN:",
"dialInNotSupported": "Markatzea ez da onartzen.",
"dialInNumber": "Markatzea:",
"dialInSummaryError": "",
"dialInTollFree": "",
"dialInSummaryError": "Huts egin du markatze-informazioa eskuratzeko. Saiatu berriro geroago.",
"dialInTollFree": "Doako deia",
"genericError": "Hara! Arazoren bat egon da.",
"inviteLiveStream": "Bilera honen zuzeneko erreprodukzioa ikusteko, egin klik esteka honetan: {{url}}",
"invitePhone": "",
"invitePhone": "Telefonoz sartzeko, markatu : {{number}},,{{conferenceID}}#\n",
"invitePhoneAlternatives": "Markatze-zenbaki ezberdin baten bila zabiltza?\nIkusi bileraren markatze-zenbakiak: {{url}}\n\n\nGela-telefono baten bidez markatzen ari bazara, batu audioa konektatu gabe: {{silentUrl}}",
"inviteURLFirstPartGeneral": "Bilera batera batzeko gonbidapena luzatu zaizu.",
"inviteURLFirstPartPersonal": "{{name}} erabiltzaileak bilera batera gonbidatu zaitu.\n",
@@ -329,12 +336,12 @@
"moreNumbers": "Zenbaki gehiago",
"noNumbers": "Markatze-zenbakirik ez.",
"noPassword": "Bat ere ez",
"noRoom": "",
"noRoom": "Ez zen zehaztu behar den gela.",
"numbers": "Markatze-zenbakiak",
"password": "$t(lockRoomPasswordUppercase):",
"title": "Partekatu",
"tooltip": "",
"label": ""
"tooltip": "Partekatu estekaren eta markaketaren inguruko informazioa bilera honetarako",
"label": "Saioari buruzko informazioa"
},
"inviteDialog": {
"alertText": "Ezin izan da parte hartzaile batzuk gonbidatu.",
@@ -345,7 +352,7 @@
"send": "Bidali"
},
"inlineDialogFailure": {
"msg": "",
"msg": "Arazotxo bat izan dugu.",
"retry": "Saiatu berriro",
"support": "Laguntza",
"supportMsg": "Hau gertatzen jarraituz gero, jarri gurekin harremanetan"
@@ -359,10 +366,10 @@
"mute": "Mututu edo desmututu mikrofonoa",
"pushToTalk": "Sakatu hitz egiteko",
"raiseHand": "Altxa edo jaitsi eskua",
"showSpeakerStats": "",
"showSpeakerStats": "Erakutsi iragarleen estatistikak",
"toggleChat": "Ireki edo itxi txata",
"toggleFilmstrip": "Erakutsi edo ezkutatu bideoaren miniaturak",
"toggleScreensharing": "",
"toggleScreensharing": "Kamera eta pantailaren partekatzea aldatu",
"toggleShortcuts": "Erakutsi edo ezkutatu teklatu lasterbideak",
"videoMute": "Abiarazi edo gelditu zure kamera",
"videoQuality": "Kudeatu deiaren kalitatea"
@@ -370,7 +377,7 @@
"liveStreaming": {
"busy": "Transmisio baliabideak libratzen ari gara. Saiatu berriro minutu batzuk barru.",
"busyTitle": "Zuzeneko emanaldia egin dezaketen guztiak okupatuta daude",
"changeSignIn": "",
"changeSignIn": "Aldatu kontua.",
"choose": "Aukeratu zuzeneko transmisioa",
"chooseCTA": "Aukeratu transmisio-aukera. Une honetan {{email}} gisa saioa hasi duzu.",
"enterStreamKey": "Sartu zure YouTube zuzeneko transmisioaren gakoa hemen.",
@@ -403,40 +410,40 @@
"clientState": {
"off": "Itzalita",
"on": "Piztuta",
"unknown": ""
"unknown": "Ezezaguna"
},
"dialogTitle": "Grabazio-kontrol lokalak",
"duration": "",
"duration": "Iraupena",
"durationNA": "E/E",
"encoding": "Kodeketa",
"label": "",
"labelToolTip": "",
"label": "GLO",
"labelToolTip": "Tokiko grabazioa gaituta dago",
"localRecording": "Grabazio lokala",
"me": "Ni",
"messages": {
"engaged": "",
"finished": "",
"engaged": "Tokiko grabazioa aktibatuta dago.",
"finished": "Grabaketa saioa {{token}} amaitu da. Mesedez, bidali grabatutako fitxategia moderatzaileari.",
"finishedModerator": "{{token}} grabazio saioa amaitu da. Pista lokalaren grabazioa gorde da. Eskatu gainerako parte-hartzaileei bere grabazioak bidaltzeko.",
"notModerator": ""
"notModerator": "Ez zara moderatzailea. Ezin duzu grabazio lokalik hasi edo gelditu."
},
"moderator": "",
"moderator": "Moderatzailea",
"no": "Ez",
"participant": "",
"participant": "partaidea",
"participantStats": "Parte-hartzaileen estatistikak",
"sessionToken": "Saioaren token-a",
"start": "",
"stop": "",
"yes": ""
"start": "Grabatzen hasi",
"stop": "Grabaketa gelditu",
"yes": "Bai"
},
"lockRoomPassword": "",
"lockRoomPasswordUppercase": "",
"lockRoomPassword": "Pasahitza",
"lockRoomPasswordUppercase": "Pasahitza",
"me": "ni",
"notify": {
"connectedOneMember": "{{name}} bilerara sartu da",
"connectedThreePlusMembers": "{{name}} eta beste {{count}} bilerara sartu dira",
"connectedTwoMembers": "{{first}} eta {{second}} bilerara sartu dira",
"disconnected": "deskonektatuta",
"focus": "",
"focus": "Konferentzia fokuratu",
"focusFail": "{{component}} ez dago eskuragarri - saiatu berriro {{ms}} segundo barru",
"grantedTo": "{{to}}(e)ri moderatzaile eskubideak eman zaizkio!",
"invitedOneMember": "{{name}} gonbidatu da",
@@ -455,16 +462,47 @@
"somebody": "Norbait",
"startSilentTitle": "Audio irteerarik gabe batu zara!",
"startSilentDescription": "Batu berriro audioa gaitzeko",
"suboptimalBrowserWarning": "",
"suboptimalBrowserWarning": "Bilera ez da ona izaten ari. Saiatu nabigatzaile egokiak erabiltzen <a href='static/recommendedBrowsers.html' target='_blank'/a>.",
"suboptimalExperienceTitle": "Nabigatzailearen abisua",
"unmute": "Desmututu",
"newDeviceCameraTitle": "Kamera berria hauteman da",
"newDeviceAudioTitle": "Audio-gailu berria hauteman da",
"newDeviceAction": "Erabilera"
"newDeviceAction": "Erabilera",
"OldElectronAPPTitle": "",
"oldElectronClientDescription1": "",
"oldElectronClientDescription2": "",
"oldElectronClientDescription3": ""
},
"passwordSetRemotely": "beste parte-hartzaile batek ezarrita",
"passwordDigitsOnly": "{{number}} digitu arte",
"poweredby": "garatzailea:",
"prejoin": {
"audioAndVideoError": "",
"audioOnlyError": "",
"audioTrackError": "",
"callMe": "",
"callMeAtNumber": "",
"configuringDevices": "",
"connectedWithAudioQ": "",
"copyAndShare": "",
"dialInMeeting": "",
"dialInPin": "",
"dialing": "",
"iWantToDialIn": "",
"joinAudioByPhone": "",
"joinMeeting": "",
"joinWithoutAudio": "",
"initiated": "",
"linkCopied": "",
"lookGood": "",
"or": "",
"calling": "",
"startWithPhone": "",
"screenSharingError": "",
"videoOnlyError": "",
"videoTrackError": "",
"viewAllNumbers": ""
},
"presenceStatus": {
"busy": "Okupatuta",
"calling": "Deitzen…",
@@ -500,164 +538,169 @@
"fileSharingdescription": "Partekatu grabazioa bileraren parte-hartzaileekin",
"live": "ZUZENEAN",
"loggedIn": "{{userName}} gisa saioa hasita",
"off": "",
"off": "Grabazioa gelditu da",
"offBy": "{{name}} erabiltzaileak grabazioa gelditu du",
"on": "",
"on": "Grabaketa",
"onBy": "{{name}} erabiltzaileak grabazioa hasi du",
"pending": "Bilera grabatzeko prestatzen…",
"rec": "GRB",
"serviceDescription": "Zure grabazioa grabazio-zerbitzuan gordeko da",
"serviceName": "Grabazio-zerbitzua",
"signIn": "",
"signIn": "Sartu",
"signOut": "Itxi saioa",
"unavailable": "",
"unavailableTitle": "Grabazioa ez dago eskuragarri"
},
"sectionList": {
"pullToRefresh": ""
"pullToRefresh": "Eguneratu"
},
"settings": {
"calendar": {
"about": "",
"about": "Egutegiaren integrazioa {{appName}} zure egutegian modu seguruan sartzeko erabiltzen da datozen gertaerak irakurri ahal izateko.",
"disconnect": "Deskonektatu",
"microsoftSignIn": "Saioa hasi Microsoftekin",
"signedIn": "",
"title": ""
"signedIn": "Une honetan {{email}} arekin egutegiko gertaeretara sartzen ari zara. Egin klik beheko botoia deskonektatzeko.",
"title": "Egutegia"
},
"devices": "Gailuak",
"followMe": "",
"language": "",
"loggedIn": "",
"followMe": "Denek jarraitzen naute",
"language": "Hizkuntza",
"loggedIn": "Saioa {{name}} gisa hasi da",
"microphones": "Mikrofonoak",
"moderator": "Moderatzailea",
"more": "Gehiago",
"name": "",
"noDevice": "",
"selectAudioOutput": "",
"selectCamera": "",
"selectMic": "",
"speakers": "",
"startAudioMuted": "",
"startVideoMuted": "",
"title": ""
"name": "Izena",
"noDevice": "Bat ere ez",
"selectAudioOutput": "Audio irteera",
"selectCamera": "Kamera",
"selectMic": "Mikrofonoa",
"speakers": "Bozgorailuak",
"startAudioMuted": "Denok isilik hasi",
"startVideoMuted": "Denak ezkutatuta hasi",
"title": "Ezarpenak"
},
"settingsView": {
"advanced": "Aurreratua",
"alertOk": "",
"alertTitle": "",
"alertURLText": "",
"alertOk": "Ados",
"alertCancel": "",
"alertTitle": "Abisua",
"alertURLText": "Zerbitzariaren URLa baliogabea da",
"buildInfoSection": "Konpilazioaren informazioa",
"conferenceSection": "",
"disableCallIntegration": "Desgaitu jatorrizko deien integrazioa",
"disableP2P": "",
"displayName": "",
"email": "",
"header": "",
"profileSection": "",
"disableP2P": "Desgaitu puntuz puntuko modua",
"disableCrashReporting": "",
"disableCrashReportingWarning": "",
"displayName": "Bistaratzeko izena",
"email": "Posta elektronikoa",
"header": "Ezarpenak",
"profileSection": "Profila",
"serverURL": "Zerbitzariaren URLa",
"showAdvanced": "",
"startWithAudioMuted": "",
"startWithVideoMuted": "",
"version": ""
"showAdvanced": "Erakutsi ezarpen aurreratuak",
"startWithAudioMuted": "Hasi audio isilean",
"startWithVideoMuted": "Hasi bideoa isilean",
"version": "Bertsioa"
},
"share": {
"dialInfoText": "",
"dialInfoText": "\n\n===== \\ n\nZure telefonoan markatu nahi duzu? \\ N\n{{defaultDialInNumber}} Egin klik esteka honetan bilera horretako telefonoak ikusteko\n{{dialInfoPageUrl}}",
"mainText": "Egin klik hurrengo estekan bilerara elkartzeko:\n{{roomUrl}}"
},
"speaker": "",
"speaker": "bozgorailua",
"speakerStats": {
"hours": "{{count}}h",
"minutes": "{{count}}m",
"name": "",
"name": "Izena",
"seconds": "{{count}}s",
"speakerStats": "",
"speakerTime": ""
"speakerStats": "Hizlariaren estatistikak",
"speakerTime": "Hizlariaren denbora"
},
"startupoverlay": {
"policyText": " ",
"title": "{{app}} aplikazioak zure mikrofonoa eta kamara erabili behar ditu."
},
"suspendedoverlay": {
"rejoinKeyTitle": "",
"text": "",
"title": ""
"rejoinKeyTitle": "Atzera",
"text": "Sakatu <i> Bildu </i> botoia berriro konektatzeko.",
"title": "Bideo-deia eten egin da ekipamendu hau gelditu delako"
},
"toolbar": {
"accessibilityLabel": {
"audioOnly": "",
"audioOnly": "Txandakatu audioa soilik",
"audioRoute": "Hautatu soinu-gailua",
"callQuality": "Kudeatu bideoaren kalitatea",
"cc": "",
"cc": "Txandakatu azpitituluak",
"chat": "Txandakatu txat leihoa",
"document": "",
"download": "",
"document": "Txandakatu partekatutako dokumentua",
"download": "Gure aplikazioak deskargatu",
"e2ee": "",
"feedback": "Utzi iruzkin bat",
"fullScreen": "",
"hangup": "",
"help": "",
"invite": "",
"hangup": "Deia bota",
"help": "Laguntza",
"invite": "Jendea gonbidatu",
"kick": "Kanporatu parte-hartzailea",
"localRecording": "",
"localRecording": "Tokiko grabazio kontrolak kontrolatu",
"lockRoom": "Aldatu bileraren pasahitza",
"moreActions": "",
"moreActionsMenu": "",
"moreActions": "Txandakatu ekintza gehiago menua",
"moreActionsMenu": "Ekintza gehiago menua",
"moreOptions": "",
"mute": "",
"muteEveryone": "",
"pip": "",
"privateMessage": "",
"profile": "",
"mute": "Isildu",
"muteEveryone": "Partehartzaile guztiak isildu",
"pip": "Txandakatu irudiaren irudian",
"privateMessage": "Mezu pribatua bidali",
"profile": "Editatu zure profila",
"raiseHand": "",
"recording": "",
"remoteMute": "",
"Settings": "",
"sharedvideo": "",
"shareRoom": "",
"recording": "Grabaketa aktibatu",
"remoteMute": "Mututu parte-hartzailea",
"Settings": "Txandakatu ezarpenak",
"sharedvideo": "Txandakatu YouTube bideoa partekatzen",
"shareRoom": "norbait gonbidatu",
"shareYourScreen": "Txandakatu pantaila partekatzea",
"shortcuts": "",
"show": "",
"speakerStats": "",
"tileView": "",
"shortcuts": "Txandakatu lasterbideak",
"show": "Erakutsi",
"speakerStats": "Txandakatu hiztunen estatistikak",
"tileView": "Txandakatu fitxa ikuspegia",
"toggleCamera": "Txandakatu kamera",
"toggleFilmstrip": "Txandakatu film-zerrenda",
"videomute": "Txandakatu mutututako bideoa",
"videoblur": ""
"videoblur": "Txandakatu bideoaren lausotzea"
},
"addPeople": "Gehitu jendea zure deira",
"audioOnlyOff": "",
"audioOnlyOn": "",
"audioRoute": "",
"authenticate": "",
"callQuality": "",
"chat": "",
"closeChat": "",
"documentClose": "",
"documentOpen": "",
"download": "",
"enterFullScreen": "",
"enterTileView": "",
"exitFullScreen": "",
"exitTileView": "",
"audioOnlyOff": "Gaitu audioa soilik modua",
"audioOnlyOn": "Gaitu audioa soilik modua",
"audioRoute": "Aukeratu soinu gailua",
"authenticate": "Autentifikatu",
"callQuality": "Kudeatu deiaren kalitatea",
"chat": "txat gela ireki / itxi",
"closeChat": "Itxi txat gela",
"documentClose": "Dokumentu partekatu itxi",
"documentOpen": "Ireki partekatutako dokumentua",
"download": "Deskargatu gure aplikazioa",
"e2ee": "",
"enterFullScreen": "Ikusi pantaila osoa",
"enterTileView": "Sartu mosaiko ikuspegian",
"exitFullScreen": "Irten pantaila osora",
"exitTileView": "Irten mosaikoaren ikuspegitik",
"feedback": "Utzi iruzkin bat",
"hangup": "",
"help": "",
"invite": "",
"login": "",
"hangup": "Irten",
"help": "Laguntza",
"invite": "Jendea gonbidatu",
"login": "Saioa hasi",
"logout": "Itxi saioa",
"lowerYourHand": "Jaitsi eskua",
"moreActions": "",
"moreOptions": "",
"mute": "",
"muteEveryone": "",
"noAudioSignalTitle": "",
"noAudioSignalDesc": "",
"noAudioSignalDescSuggestion": "",
"moreActions": "Ekintza gehiago",
"moreOptions": "Aukera gehiago",
"mute": "Isiltasuna aktibatu / desaktibatu",
"muteEveryone": "Partehartzaile guztiak isildu",
"noAudioSignalTitle": "Mikrofonotik ez dago sarrerarik!",
"noAudioSignalDesc": "Sistema edo gailuaren ezarpenetatik islatu ez baduzu, kontuan hartu gailua aldatzea.",
"noAudioSignalDescSuggestion": "Sistema edo gailuaren ezarpenetatik islatu ez baduzu, kontuan hartu hurrengo gailua erabiltzea:",
"noAudioSignalDialInDesc": "",
"noAudioSignalDialInLinkDesc": "",
"noisyAudioInputTitle": "",
"noisyAudioInputDesc": "",
"noisyAudioInputTitle": "ingurune zaratatsua",
"noisyAudioInputDesc": "ingurune zaratatsua",
"openChat": "Ireki txata",
"pip": "",
"pip": "Irudi-irudian sartu",
"privateMessage": "Bidali mezu pribatua",
"profile": "Editatu zure profila",
"raiseHand": "Altxa / jaitsi eskua",
@@ -665,74 +708,74 @@
"Settings": "Ezarpenak",
"sharedvideo": "Partekatu YouTube bideoa",
"shareRoom": "Gonbidatu norbait",
"shortcuts": "",
"speakerStats": "",
"startScreenSharing": "",
"startSubtitles": "",
"stopScreenSharing": "",
"stopSubtitles": "",
"stopSharedVideo": "",
"talkWhileMutedPopup": "",
"tileViewToggle": "",
"toggleCamera": "",
"videomute": "",
"startvideoblur": "",
"stopvideoblur": ""
"shortcuts": "Ikusi lasterbideak",
"speakerStats": "Hizlariaren estatistikak",
"startScreenSharing": "Hasi pantaila partekatzen",
"startSubtitles": "Azpitituluak hasi",
"stopScreenSharing": "Gelditu pantaila partekatzea",
"stopSubtitles": "Azpitituluak gelditu",
"stopSharedVideo": "YouTuben bideoa gelditu",
"talkWhileMutedPopup": "Hitz egiten saiatzen ari al zara? Mututa zaude.",
"tileViewToggle": "Txandakatu fitxa ikuspegia",
"toggleCamera": "Txandakatu kamera",
"videomute": "Hasi / gelditu kamera",
"startvideoblur": "Bideo lausoa hasi",
"stopvideoblur": "Bideo lausoa gelditu"
},
"transcribing": {
"ccButtonTooltip": "",
"error": "",
"expandedLabel": "",
"failedToStart": "",
"labelToolTip": "",
"off": "",
"pending": "",
"start": "",
"stop": "",
"tr": ""
"ccButtonTooltip": "Hasi / Gelditu Azpitituluak",
"error": "Transkripzioak huts egin du. Saiatu berriro.",
"expandedLabel": "Transkripzioa aktibatuta",
"failedToStart": "Ezin da transkripzioa hasi",
"labelToolTip": "Bilera transkribatzen ari da",
"off": "Transkripzioa gelditu da",
"pending": "Bileraren transkripzioa prestatzen ...",
"start": "Erakutsi azpitituluak",
"stop": "Utzi azpitituluak erakusten",
"tr": "TR"
},
"userMedia": {
"androidGrantPermissions": "Hautatu <b><i>Baimendu</i></b> zure nabigatzaileak baimenak eskatzen dituenean.",
"chromeGrantPermissions": "Hautatu <b><i>Baimendu</i></b> zure nabigatzaileak baimenak eskatzen dituenean.",
"edgeGrantPermissions": "Hautatu <b><i>Bai</i></b> zure nabigatzaileak baimenak eskatzen dituenean.",
"electronGrantPermissions": "",
"electronGrantPermissions": "Mesedez eman zure kamera eta mikrofonoa erabiltzeko baimenak",
"firefoxGrantPermissions": "Hautatu <b><i>Partekatutako hautatutako gailua</i></b> zure nabigatzaileak baimenak eskatzen dituenean.",
"iexplorerGrantPermissions": "Hautatu <b><i>Ados</i></b> zure nabigatzaileak baimenak eskatzen dituenean.",
"nwjsGrantPermissions": "",
"nwjsGrantPermissions": "Mesedez, eman zure kamera eta mikrofonoa erabiltzeko baimenak",
"operaGrantPermissions": "Hautatu <b><i>Baimendu</i></b> zure nabigatzaileak baimenak eskatzen dituenean.",
"react-nativeGrantPermissions": "Hautatu <b><i>Baimendu</i></b> zure nabigatzaileak baimenak eskatzen dituenean.",
"safariGrantPermissions": "Hautatu <b><i>Ados</i></b> zure nabigatzaileak baimenak eskatzen dituenean."
},
"videoSIPGW": {
"busy": "Baliabideak askatzeko lanetan gabiltza. Mesedez, saiatu berriro minutu batzuk barru.",
"busyTitle": "",
"busyTitle": "Gela zerbitzua lanpetuta dago une honetan",
"errorAlreadyInvited": "{{displayName}} jada gonbidatuta dago",
"errorInvite": "",
"errorInviteFailed": "",
"errorInviteFailedTitle": "",
"errorInviteTitle": "",
"errorInvite": "Konferentzia oraindik ez da finkatu. Saiatu berriro geroago.",
"errorInviteFailed": "Egoera konpontzeko lanean gabiltza. Saiatu berriro geroago.",
"errorInviteFailedTitle": "{{displayName}}} huts egin du",
"errorInviteTitle": "Errorea gela gonbidatzea",
"pending": "{{displayName}} gonbidatu da"
},
"videoStatus": {
"audioOnly": "",
"audioOnlyExpanded": "",
"callQuality": "",
"hd": "",
"hdTooltip": "",
"highDefinition": "",
"labelTooiltipNoVideo": "",
"labelTooltipAudioOnly": "",
"ld": "",
"ldTooltip": "",
"lowDefinition": "",
"onlyAudioAvailable": "",
"onlyAudioSupported": "",
"p2pEnabled": "",
"p2pVideoQualityDescription": "",
"recHighDefinitionOnly": "",
"sd": "",
"sdTooltip": "",
"standardDefinition": ""
"audioOnly": "AUD",
"audioOnlyExpanded": "Audio-soilik moduan zaude. Modu honek banda zabalera gordetzen du, baina besteen bideoa ez duzu ikusiko.",
"callQuality": "Bideo kalitatea",
"hd": "HD",
"hdTooltip": "Bideoa definizio-altuan ikusten",
"highDefinition": "Definizio-altuan moduan dago",
"labelTooiltipNoVideo": "Ez dago bideorik",
"labelTooltipAudioOnly": "Audio-soilik moduan dago",
"ld": "LD",
"ldTooltip": "Bideoa behe-definizioan ikusten",
"lowDefinition": "Behe-definizioa",
"onlyAudioAvailable": "Soilik audioa dago erabilgarri",
"onlyAudioSupported": "Arakatzaile honetan soilik onartzen dugu audioa.",
"p2pEnabled": "Peer-to-peer modua aktibatuta",
"p2pVideoQualityDescription": "Peer-to-peer moduan, jasotako bideoaren kalitatea, kalitate handiko eta audiosolik moduen artean txandakatu. Beste aukera batzuk ez dira prest egongo puntuz puntuko modutik irten arte.",
"recHighDefinitionOnly": "Definizio handiagoa nahiago izango duzu.",
"sd": "SD",
"sdTooltip": "Definizio estandarrean bideoa ikusten",
"standardDefinition": "Definizio estandarra"
},
"videothumbnail": {
"domute": "Mututu",
@@ -743,8 +786,8 @@
"mute": "Parte-hartzailea mutututa dago",
"muted": "Mutututa",
"remoteControl": "Hasi / Gelditu urruneko kontrola",
"show": "",
"videomute": ""
"show": "Erakutsi",
"videomute": "Parte-hartzaileak kamera geldiarazi du."
},
"welcomepage": {
"accessibilityLabel": {
@@ -766,7 +809,7 @@
"goSmall": "JOAN",
"join": "SORTU / SARTU",
"info": "Informazioa",
"privacy": "",
"privacy": "Pribatutasuna",
"recentList": "Azkenak",
"recentListDelete": "Ezabatu",
"recentListEmpty": "Azkenak zerrenda hutsik dago. Txateatu zure taldearekin eta azken bilera guztiak hemen aurkituko dituzu.",

View File

@@ -488,13 +488,14 @@
"dialInMeeting": "Dial into the meeting",
"dialInPin": "Dial into the meeting and enter PIN code:",
"dialing": "Dialing",
"doNotShow": "Don't show this again",
"iWantToDialIn": "I want to dial in",
"joinAudioByPhone": "Join with phone audio",
"joinMeeting": "Join meeting",
"joinWithoutAudio": "Join without audio",
"initiated": "Call initiated",
"linkCopied": "Link copied to clipboard",
"lookGood": "Speaker and microphone look good",
"lookGood": "It sounds like your microphone is working properly",
"or": "or",
"calling": "Calling",
"startWithPhone": "Start with phone audio",

View File

@@ -18,7 +18,7 @@ import {
SET_AUDIO_INPUT_DEVICE,
SET_VIDEO_INPUT_DEVICE
} from './actionTypes';
import { replaceAudioTrackById, replaceVideoTrackById } from '../../prejoin/actions';
import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
import { isPrejoinPageVisible } from '../../prejoin/functions';
import { showNotification, showWarningNotification } from '../../notifications';
import { updateSettings } from '../settings';
@@ -65,14 +65,19 @@ MiddlewareRegistry.register(store => next => action => {
|| JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
.camera[JitsiTrackErrors.GENERAL];
const additionalCameraErrorMsg = cameraJitsiTrackErrorMsg ? null : message;
const titleKey = name === JitsiTrackErrors.PERMISSION_DENIED
? 'deviceError.cameraPermission' : 'deviceError.cameraError';
store.dispatch(showWarningNotification({
description: additionalCameraErrorMsg,
descriptionKey: cameraErrorMsg,
titleKey: name === JitsiTrackErrors.PERMISSION_DENIED
? 'deviceError.cameraPermission' : 'deviceError.cameraError'
titleKey
}));
if (isPrejoinPageVisible(store.getState())) {
store.dispatch(setDeviceStatusWarning(titleKey));
}
break;
}
case NOTIFY_MIC_ERROR: {
@@ -88,15 +93,20 @@ MiddlewareRegistry.register(store => next => action => {
|| JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
.microphone[JitsiTrackErrors.GENERAL];
const additionalMicErrorMsg = micJitsiTrackErrorMsg ? null : message;
const titleKey = name === JitsiTrackErrors.PERMISSION_DENIED
? 'deviceError.microphonePermission'
: 'deviceError.microphoneError';
store.dispatch(showWarningNotification({
description: additionalMicErrorMsg,
descriptionKey: micErrorMsg,
titleKey: name === JitsiTrackErrors.PERMISSION_DENIED
? 'deviceError.microphonePermission'
: 'deviceError.microphoneError'
titleKey
}));
if (isPrejoinPageVisible(store.getState())) {
store.dispatch(setDeviceStatusWarning(titleKey));
}
break;
}
case SET_AUDIO_INPUT_DEVICE:

View File

@@ -85,3 +85,4 @@ export { default as IconVideoQualityLD } from './LD.svg';
export { default as IconVideoQualitySD } from './SD.svg';
export { default as IconVolume } from './volume.svg';
export { default as IconVolumeEmpty } from './volume-empty.svg';
export { default as IconVolumeOff } from './volume-off.svg';

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.8887 6.42609L7.87494 6.43755L3.73524 2.29786C3.33819 1.9008 2.69455 1.9007 2.29763 2.29763C1.9007 2.69455 1.9008 3.33819 2.29786 3.73524L6.30688 7.74427L6 8H3C2.44772 8 2 8.44772 2 9V15C2 15.5523 2.44772 16 3 16H6L11.1799 20.3166C11.2698 20.3915 11.383 20.4325 11.5 20.4325C11.7761 20.4325 12 20.2086 12 19.9325V13.4374L14 15.4374V16C14.179 16 14.3553 15.9882 14.5281 15.9655L16.1615 17.5989C15.4909 17.8579 14.762 18 14 18V20C15.3237 20 16.5723 19.6785 17.672 19.1094L20.2648 21.7021C20.6618 22.0992 21.3055 22.0993 21.7024 21.7024C22.0993 21.3055 22.0992 20.6618 21.7021 20.2648L19.3686 17.9312C19.373 17.9272 19.3774 17.9232 19.3818 17.9192L17.9655 16.5029C17.961 16.5068 17.9565 16.5107 17.9521 16.5147L16.5332 15.0958C16.5378 15.092 16.5424 15.0882 16.547 15.0844L15.1203 13.6577C15.1153 13.6611 15.1103 13.6645 15.1052 13.6678L12 10.5626V10.5374L10 8.53739V8.56261L9.29498 7.8576L9.30874 7.84613L7.8887 6.42609ZM14 9.66261L17.7452 13.4078C17.9099 12.9699 18 12.4955 18 12C18 9.79086 16.2091 8 14 8V9.66261ZM19.248 14.9106L20.7042 16.3668C21.5237 15.1112 22 13.6112 22 12C22 7.58172 18.4183 4 14 4V6C17.3137 6 20 8.68629 20 12C20 13.0562 19.7271 14.0486 19.248 14.9106ZM12 7.66261L9.45676 5.11937L11.1799 3.68341C11.392 3.50663 11.7073 3.53529 11.8841 3.74743C11.959 3.83729 12 3.95055 12 4.06752V7.66261ZM6.7241 10L7.72692 9.16431L10 11.4374V16.7299L6.7241 14H4V10H6.7241Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -32,6 +32,17 @@ export function getCurrentOutputDeviceId(state: Object) {
return state['features/base/settings'].audioOutputDeviceId;
}
/**
* Returns the saved display name.
*
* @param {Object} state - The state of the application.
* @returns {string}
*/
export function getDisplayName(state: Object): string {
return state['features/base/settings'].displayName || '';
}
/**
* Handles changes to the `disableCallIntegration` setting.
* Noop on web.

View File

@@ -39,7 +39,8 @@ const DEFAULT_STATE = {
userSelectedMicDeviceId: undefined,
userSelectedAudioOutputDeviceLabel: undefined,
userSelectedCameraDeviceLabel: undefined,
userSelectedMicDeviceLabel: undefined
userSelectedMicDeviceLabel: undefined,
userSelectedSkipPrejoin: undefined
};
const STORE_NAME = 'features/base/settings';

View File

@@ -165,7 +165,7 @@ class DeepLinkingMobilePage extends Component<Props> {
_generateDownloadURL() {
const url = _URLS[Platform.OS];
if (url) {
if (url && typeof interfaceConfig.MOBILE_DYNAMIC_LINK === 'undefined') {
return url;
}
@@ -175,12 +175,15 @@ class DeepLinkingMobilePage extends Component<Props> {
const {
APN = 'org.jitsi.meet',
APP_CODE = 'w2atb',
CUSTOM_DOMAIN = undefined,
IBI = 'com.atlassian.JitsiMeet.ios',
ISI = '1165103905'
} = interfaceConfig.MOBILE_DYNAMIC_LINK || {};
const domain = CUSTOM_DOMAIN ?? `https://${APP_CODE}.app.goo.gl`;
const IUS = interfaceConfig.APP_SCHEME || 'org.jitsi.meet';
return `https://${APP_CODE}.app.goo.gl/?link=${
return `${domain}/?link=${
encodeURIComponent(window.location.href)}&apn=${
APN}&ibi=${
IBI}&isi=${

View File

@@ -10,12 +10,13 @@ import {
CONFERENCE_JOINED,
getCurrentConference
} from '../../base/conference';
import { MiddlewareRegistry } from '../../base/redux';
import { getParticipantCount } from '../../base/participants';
import { MiddlewareRegistry, StateListenerRegistry } from '../../base/redux';
import { _SET_AUDIOMODE_DEVICES, _SET_AUDIOMODE_SUBSCRIPTIONS } from './actionTypes';
import logger from './logger';
const { AudioMode } = NativeModules;
const { AudioMode, RNCallKit } = NativeModules;
const AudioModeEmitter = new NativeEventEmitter(AudioMode);
/**
@@ -42,7 +43,7 @@ MiddlewareRegistry.register(store => next => action => {
}
case APP_WILL_MOUNT:
_appWillMount(store);
case CONFERENCE_FAILED: // eslint-disable-line no-fallthrough
case CONFERENCE_FAILED:
case CONFERENCE_LEFT:
/*
@@ -65,6 +66,16 @@ MiddlewareRegistry.register(store => next => action => {
return next(action);
});
StateListenerRegistry.register(
/* selector */ state => {
const conference = getCurrentConference(state);
const participantCount = getParticipantCount(state);
return conference && participantCount > 1;
},
/* listener */ (enableAudio, store) => _maybeEnableAudio(enableAudio, store)
);
/**
* Notifies this feature that the action {@link APP_WILL_MOUNT} is being
* dispatched within a specific redux {@code store}.
@@ -85,6 +96,28 @@ function _appWillMount(store) {
});
}
/**
* Applies only to iOS. Enables / disables the Audio Unit. When the audio unit is
* enabled audio will flow on both directions.
*
* @param {boolean} enabled - Whether audio should be enabled or not.
* @param {Store} store - The redux store.
* @private
* @returns {void}
*/
function _maybeEnableAudio(enabled, { getState }) {
// XXX: Ideally we'd want to call AudioMode.setAudioEnabled here, but that too won't work
// without telling CallKit to hold the call. Yeah, I know we don't support it, but hey
// thiss was the only workaround that ddid the job.
if (RNCallKit) {
const conference = getCurrentConference(getState);
if (conference && conference.callUUID) {
RNCallKit.setCallOnHold(conference.callUUID, !enabled);
}
}
}
/**
* Handles audio device changes. The list will be stored on the redux store.
*

View File

@@ -46,6 +46,10 @@ if (CallKit) {
'performEndCallAction',
delegate._onPerformEndCallAction,
context),
CallKit.addListener(
'performSetHeldCallAction',
delegate._onPerformSetHeldCallAction,
context),
CallKit.addListener(
'performSetMutedCallAction',
delegate._onPerformSetMutedCallAction,

View File

@@ -113,6 +113,7 @@ function _appWillMount({ dispatch, getState }, next, action) {
};
const delegate = {
_onPerformSetHeldCallAction,
_onPerformSetMutedCallAction,
_onPerformEndCallAction
};
@@ -363,6 +364,24 @@ function _onPerformEndCallAction({ callUUID }) {
}
}
/**
* Handles CallKit's event {@code performSetHeldCallAction}.
*
* @param {Object} event - The details of the CallKit event
* {@code performSetHeldCallAction}.
* @returns {void}
*/
function _onPerformSetHeldCallAction({ callUUID, onHold }) {
const { getState } = this; // eslint-disable-line no-invalid-this
const conference = getCurrentConference(getState);
if (conference && conference.callUUID === callUUID) {
if (AudioMode.setAudioEnabled) {
AudioMode.setAudioEnabled(!onHold);
}
}
}
/**
* Handles CallKit's event {@code performSetMutedCallAction}.
*

View File

@@ -24,6 +24,11 @@ export const PREJOIN_START_CONFERENCE = 'PREJOIN_START_CONFERENCE';
*/
export const SET_DEVICE_STATUS = 'SET_DEVICE_STATUS';
/**
* Action type to set the visiblity of the prejoin page for the future.
*/
export const SET_SKIP_PREJOIN = 'SET_SKIP_PREJOIN';
/**
* Action type to set the visiblity of the 'JoinByPhone' dialog.
*/
@@ -44,11 +49,6 @@ export const SET_PREJOIN_AUDIO_MUTED = 'SET_PREJOIN_AUDIO_MUTED';
*/
export const SET_PREJOIN_DEVICE_ERRORS = 'SET_PREJOIN_DEVICE_ERRORS';
/**
* Action type to set the name of the user.
*/
export const SET_PREJOIN_NAME = 'SET_PREJOIN_NAME';
/**
* Action type to set the visibility of the prejoin page.
*/

View File

@@ -6,11 +6,11 @@ import {
ADD_PREJOIN_VIDEO_TRACK,
PREJOIN_START_CONFERENCE,
SET_DEVICE_STATUS,
SET_SKIP_PREJOIN,
SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
SET_PREJOIN_AUDIO_DISABLED,
SET_PREJOIN_AUDIO_MUTED,
SET_PREJOIN_DEVICE_ERRORS,
SET_PREJOIN_NAME,
SET_PREJOIN_PAGE_VISIBILITY,
SET_PREJOIN_VIDEO_DISABLED,
SET_PREJOIN_VIDEO_MUTED
@@ -273,6 +273,18 @@ export function setDeviceStatusWarning(deviceStatusText: string) {
};
}
/**
* Sets the visibility of the prejoin page for future uses.
*
* @param {boolean} value - The visibility value.
* @returns {Object}
*/
export function setSkipPrejoin(value: boolean) {
return {
type: SET_SKIP_PREJOIN,
value
};
}
/**
* Action used to set the visiblitiy of the 'JoinByPhoneDialog'.
@@ -300,19 +312,6 @@ export function setPrejoinDeviceErrors(value: Object) {
};
}
/**
* Action used to set the name of the guest user.
*
* @param {string} value - The name.
* @returns {Object}
*/
export function setPrejoinName(value: string) {
return {
type: SET_PREJOIN_NAME,
value
};
}
/**
* Action used to set the visiblity of the prejoin page.
*

View File

@@ -1,19 +1,21 @@
// @flow
import React, { Component } from 'react';
import InlineDialog from '@atlaskit/inline-dialog';
import {
joinConference as joinConferenceAction,
joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction,
setPrejoinName
setSkipPrejoin as setSkipPrejoinAction,
setJoinByPhoneDialogVisiblity as setJoinByPhoneDialogVisiblityAction
} from '../actions';
import { getRoomName } from '../../base/conference';
import { Icon, IconPhone, IconVolumeOff } from '../../base/icons';
import { translate } from '../../base/i18n';
import { connect } from '../../base/redux';
import { getDisplayName, updateSettings } from '../../base/settings';
import ActionButton from './buttons/ActionButton';
import {
areJoinByPhoneButtonsVisible,
getPrejoinName,
isDeviceStatusVisible,
isJoinByPhoneDialogVisible
} from '../functions';
@@ -52,9 +54,9 @@ type Props = {
name: string,
/**
* Sets the name for the joining user.
* Updates settings.
*/
setName: Function,
updateSettings: Function,
/**
* The name of the meeting that is about to be joined.
@@ -62,7 +64,12 @@ type Props = {
roomName: string,
/**
* Sets visibilit of the 'JoinByPhoneDialog'.
* Sets visibility of the prejoin page for the next sessions.
*/
setSkipPrejoin: Function,
/**
* Sets visibility of the 'JoinByPhoneDialog'.
*/
setJoinByPhoneDialogVisiblity: Function,
@@ -74,7 +81,7 @@ type Props = {
/**
* If join by phone buttons should be visible.
*/
showJoinByPhoneButtons: boolean,
hasJoinByPhoneButtons: boolean,
/**
* Used for translation.
@@ -82,10 +89,18 @@ type Props = {
t: Function,
};
type State = {
/**
* Flag controlling the visibility of the 'join by phone' buttons.
*/
showJoinByPhoneButtons: boolean
}
/**
* This component is displayed before joining a meeting.
*/
class Prejoin extends Component<Props> {
class Prejoin extends Component<Props, State> {
/**
* Initializes a new {@code Prejoin} instance.
*
@@ -94,7 +109,69 @@ class Prejoin extends Component<Props> {
constructor(props) {
super(props);
this.state = {
showJoinByPhoneButtons: false
};
this._showDialog = this._showDialog.bind(this);
this._onCheckboxChange = this._onCheckboxChange.bind(this);
this._onDropdownClose = this._onDropdownClose.bind(this);
this._onOptionsClick = this._onOptionsClick.bind(this);
this._setName = this._setName.bind(this);
}
_onCheckboxChange: () => void;
/**
* Handler for the checkbox.
*
* @param {Object} e - The synthetic event.
* @returns {void}
*/
_onCheckboxChange(e) {
this.props.setSkipPrejoin(e.target.checked);
}
_onDropdownClose: () => void;
/**
* Closes the dropdown.
*
* @returns {void}
*/
_onDropdownClose() {
this.setState({
showJoinByPhoneButtons: false
});
}
_onOptionsClick: () => void;
/**
* Displays the join by phone buttons dropdown.
*
* @param {Object} e - The synthetic event.
* @returns {void}
*/
_onOptionsClick(e) {
e.stopPropagation();
this.setState({
showJoinByPhoneButtons: !this.state.showJoinByPhoneButtons
});
}
_setName: () => void;
/**
* Sets the guest participant name.
*
* @param {string} displayName - Participant name.
* @returns {void}
*/
_setName(displayName) {
this.props.updateSettings({
displayName
});
}
_showDialog: () => void;
@@ -121,49 +198,78 @@ class Prejoin extends Component<Props> {
joinConference,
joinConferenceWithoutAudio,
name,
setName,
showJoinByPhoneButtons,
hasJoinByPhoneButtons,
t
} = this.props;
const { _showDialog } = this;
const { _onCheckboxChange, _onDropdownClose, _onOptionsClick, _setName, _showDialog } = this;
const { showJoinByPhoneButtons } = this.state;
return (
<div className = 'prejoin-full-page'>
<Preview />
<Preview name = { name } />
<div className = 'prejoin-input-area-container'>
<div className = 'prejoin-input-area'>
<div className = 'prejoin-title'>
{t('prejoin.joinMeeting')}
</div>
<CopyMeetingUrl />
<ParticipantName
isEditable = { isAnonymousUser }
setName = { setName }
joinConference = { joinConference }
setName = { _setName }
value = { name } />
<ActionButton
onClick = { joinConference }
type = 'primary'>
{ t('calendarSync.join') }
</ActionButton>
{showJoinByPhoneButtons
&& <div className = 'prejoin-text-btns'>
<div className = 'prejoin-preview-dropdown-container'>
<InlineDialog
content = { <div className = 'prejoin-preview-dropdown-btns'>
<div
className = 'prejoin-preview-dropdown-btn'
onClick = { joinConferenceWithoutAudio }>
<Icon
className = 'prejoin-preview-dropdown-icon'
size = { 24 }
src = { IconVolumeOff } />
{ t('prejoin.joinWithoutAudio') }
</div>
<div
className = 'prejoin-preview-dropdown-btn'
onClick = { _showDialog }>
<Icon
className = 'prejoin-preview-dropdown-icon'
size = { 24 }
src = { IconPhone } />
{ t('prejoin.joinAudioByPhone') }
</div>
</div> }
isOpen = { showJoinByPhoneButtons }
onClose = { _onDropdownClose }>
<ActionButton
onClick = { joinConferenceWithoutAudio }
type = 'text'>
{ t('prejoin.joinWithoutAudio') }
hasOptions = { hasJoinByPhoneButtons }
onClick = { joinConference }
onOptionsClick = { _onOptionsClick }
type = 'primary'>
{ t('prejoin.joinMeeting') }
</ActionButton>
<ActionButton
onClick = { _showDialog }
type = 'text'>
{ t('prejoin.joinAudioByPhone') }
</ActionButton>
</div>}
</InlineDialog>
</div>
<div className = 'prejoin-preview-btn-container'>
<AudioSettingsButton visible = { true } />
<VideoSettingsButton visible = { true } />
</div>
</div>
<div className = 'prejoin-checkbox-container'>
<input
className = 'prejoin-checkbox'
onChange = { _onCheckboxChange }
type = 'checkbox' />
<span>{t('prejoin.doNotShow')}</span>
</div>
</div>
<div className = 'prejoin-preview-btn-container'>
<AudioSettingsButton visible = { true } />
<VideoSettingsButton visible = { true } />
</div>
{ deviceStatusVisible && <DeviceStatus /> }
</div>
);
@@ -180,10 +286,10 @@ function mapStateToProps(state): Object {
return {
isAnonymousUser: isGuest(state),
deviceStatusVisible: isDeviceStatusVisible(state),
name: getPrejoinName(state),
name: getDisplayName(state),
roomName: getRoomName(state),
showDialog: isJoinByPhoneDialogVisible(state),
showJoinByPhoneButtons: areJoinByPhoneButtonsVisible(state)
hasJoinByPhoneButtons: areJoinByPhoneButtonsVisible(state)
};
}
@@ -191,7 +297,8 @@ const mapDispatchToProps = {
joinConferenceWithoutAudio: joinConferenceWithoutAudioAction,
joinConference: joinConferenceAction,
setJoinByPhoneDialogVisiblity: setJoinByPhoneDialogVisiblityAction,
setName: setPrejoinName
setSkipPrejoin: setSkipPrejoinAction,
updateSettings
};
export default connect(mapStateToProps, mapDispatchToProps)(translate(Prejoin));

View File

@@ -1,6 +1,8 @@
// @flow
import React from 'react';
import { Icon, IconArrowDown } from '../../../base/icons';
const classNameByType = {
primary: 'prejoin-btn--primary',
secondary: 'prejoin-btn--secondary',
@@ -19,6 +21,11 @@ type Props = {
*/
className?: string,
/**
* If the button has options.
*/
hasOptions?: boolean,
/**
* The type of th button: primary, secondary, text.
*/
@@ -28,6 +35,11 @@ type Props = {
* OnClick button handler.
*/
onClick: Function,
/**
* Click handler for options.
*/
onOptionsClick?: Function
};
/**
@@ -35,7 +47,7 @@ type Props = {
*
* @returns {ReactElement}
*/
function ActionButton({ children, className, type, onClick }: Props) {
function ActionButton({ children, className, hasOptions, type, onClick, onOptionsClick }: Props) {
const ownClassName = `prejoin-btn ${classNameByType[type]}`;
const cls = className ? `${className} ${ownClassName}` : ownClassName;
@@ -44,6 +56,15 @@ function ActionButton({ children, className, type, onClick }: Props) {
className = { cls }
onClick = { onClick }>
{children}
{hasOptions && <div
className = 'prejoin-btn-options'
onClick = { onOptionsClick }>
<Icon
className = 'prejoin-btn-icon'
size = { 14 }
src = { IconArrowDown } />
</div>
}
</div>
);
}

View File

@@ -170,6 +170,7 @@ class CopyMeetingUrl extends Component<Props, State> {
</div>}
<Icon
className = { `prejoin-copy-icon ${iconCls}` }
onClick = { _copyUrl }
size = { 24 }
src = { src } />
<textarea

View File

@@ -10,6 +10,11 @@ type Props = {
*/
isEditable: boolean,
/**
* Joins the current meeting.
*/
joinConference: Function,
/**
* Sets the name for the joining user.
*/
@@ -32,6 +37,7 @@ type Props = {
* @returns {ReactElement}
*/
class ParticipantName extends Component<Props> {
/**
* Initializes a new {@code ParticipantName} instance.
*
@@ -41,9 +47,24 @@ class ParticipantName extends Component<Props> {
constructor(props) {
super(props);
this._onKeyDown = this._onKeyDown.bind(this);
this._onNameChange = this._onNameChange.bind(this);
}
_onKeyDown: () => void;
/**
* Joins the conference on 'Enter'.
*
* @param {Event} event - Key down event object.
* @returns {void}
*/
_onKeyDown(event) {
if (event.key === 'Enter') {
this.props.joinConference();
}
}
_onNameChange: () => void;
/**
@@ -63,15 +84,23 @@ class ParticipantName extends Component<Props> {
*/
render() {
const { value, isEditable, t } = this.props;
const { _onKeyDown, _onNameChange } = this;
return isEditable ? (
<input
autoFocus = { true }
className = 'prejoin-preview-name prejoin-preview-name--editable'
onChange = { this._onNameChange }
onChange = { _onNameChange }
onKeyDown = { _onKeyDown }
placeholder = { t('dialog.enterDisplayName') }
value = { value } />
)
: <div className = 'prejoin-preview-name'>{value}</div>
: <div
className = 'prejoin-preview-name'
onKeyDown = { _onKeyDown }
tabIndex = '0' >
{value}
</div>
;
}
}

View File

@@ -4,7 +4,7 @@ import React from 'react';
import { Avatar } from '../../../base/avatar';
import { Video } from '../../../base/media';
import { connect } from '../../../base/redux';
import { getActiveVideoTrack, getPrejoinName, isPrejoinVideoMuted } from '../../functions';
import { getActiveVideoTrack, isPrejoinVideoMuted } from '../../functions';
export type Props = {
@@ -41,6 +41,7 @@ function Preview(props: Props) {
return (
<div className = 'prejoin-preview'>
<div className = 'prejoin-preview-overlay' />
<div className = 'prejoin-preview-bottom-overlay' />
<Video
className = 'flipVideoX prejoin-preview-video'
videoTrack = {{ jitsiTrack: videoTrack }} />
@@ -66,7 +67,6 @@ function Preview(props: Props) {
*/
function mapStateToProps(state) {
return {
name: getPrejoinName(state),
videoTrack: getActiveVideoTrack(state),
showCameraPreview: !isPrejoinVideoMuted(state)
};

View File

@@ -93,7 +93,7 @@ export async function getAllPrejoinConfiguredTracks(state: Object): Promise<Obje
* @returns {Object}
*/
export function getAudioTrack(state: Object): Object {
return state['features/prejoin'].audioTrack;
return state['features/prejoin']?.audioTrack;
}
/**
@@ -103,7 +103,7 @@ export function getAudioTrack(state: Object): Object {
* @returns {Object}
*/
export function getContentSharingTrack(state: Object): Object {
return state['features/prejoin'].contentSharingTrack;
return state['features/prejoin']?.contentSharingTrack;
}
/**
@@ -113,7 +113,7 @@ export function getContentSharingTrack(state: Object): Object {
* @returns {string}
*/
export function getDeviceStatusText(state: Object): string {
return state['features/prejoin'].deviceStatusText;
return state['features/prejoin']?.deviceStatusText;
}
/**
@@ -123,7 +123,7 @@ export function getDeviceStatusText(state: Object): string {
* @returns {string}
*/
export function getDeviceStatusType(state: Object): string {
return state['features/prejoin'].deviceStatusType;
return state['features/prejoin']?.deviceStatusType;
}
/**
@@ -133,7 +133,7 @@ export function getDeviceStatusType(state: Object): string {
* @returns {Object}
*/
export function getVideoTrack(state: Object): Object {
return state['features/prejoin'].videoTrack;
return state['features/prejoin']?.videoTrack;
}
/**
@@ -143,17 +143,7 @@ export function getVideoTrack(state: Object): Object {
* @returns {boolean}
*/
export function isPrejoinAudioMuted(state: Object): boolean {
return state['features/prejoin'].audioMuted;
}
/**
* Selector for getting the name that the user filled while configuring.
*
* @param {Object} state - The state of the app.
* @returns {boolean}
*/
export function getPrejoinName(state: Object): string {
return state['features/prejoin'].name;
return state['features/prejoin']?.audioMuted;
}
/**
@@ -163,7 +153,7 @@ export function getPrejoinName(state: Object): string {
* @returns {boolean}
*/
export function isPrejoinVideoMuted(state: Object): boolean {
return state['features/prejoin'].videoMuted;
return state['features/prejoin']?.videoMuted;
}
/**
@@ -173,7 +163,7 @@ export function isPrejoinVideoMuted(state: Object): boolean {
* @returns {string}
*/
export function getRawError(state: Object): string {
return state['features/prejoin'].rawError;
return state['features/prejoin']?.rawError;
}
/**
@@ -183,7 +173,7 @@ export function getRawError(state: Object): string {
* @returns {boolean}
*/
export function isAudioDisabled(state: Object): Object {
return state['features/prejoin'].audioDisabled;
return state['features/prejoin']?.audioDisabled;
}
/**
@@ -193,7 +183,7 @@ export function isAudioDisabled(state: Object): Object {
* @returns {boolean}
*/
export function isPrejoinVideoDisabled(state: Object): Object {
return state['features/prejoin'].videoDisabled;
return state['features/prejoin']?.videoDisabled;
}
/**
@@ -203,7 +193,7 @@ export function isPrejoinVideoDisabled(state: Object): Object {
* @returns {boolean}
*/
export function isJoinByPhoneDialogVisible(state: Object): boolean {
return state['features/prejoin'].showJoinByPhoneDialog;
return state['features/prejoin']?.showJoinByPhoneDialog;
}
/**
@@ -214,7 +204,8 @@ export function isJoinByPhoneDialogVisible(state: Object): boolean {
* @returns {boolean}
*/
export function isPrejoinPageEnabled(state: Object): boolean {
return state['features/base/config'].prejoinPageEnabled;
return state['features/base/config'].prejoinPageEnabled
&& !state['features/base/settings'].userSelectedSkipPrejoin;
}
/**
@@ -224,5 +215,5 @@ export function isPrejoinPageEnabled(state: Object): boolean {
* @returns {boolean}
*/
export function isPrejoinPageVisible(state: Object): boolean {
return isPrejoinPageEnabled(state) && state['features/prejoin'].showPrejoin;
return isPrejoinPageEnabled(state) && state['features/prejoin']?.showPrejoin;
}

View File

@@ -6,11 +6,10 @@ import {
PREJOIN_START_CONFERENCE
} from './actionTypes';
import { setPrejoinAudioMuted, setPrejoinVideoMuted } from './actions';
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media';
import { participantUpdated, getLocalParticipant } from '../base/participants';
import { MiddlewareRegistry } from '../base/redux';
import { updateSettings } from '../base/settings';
import { getAllPrejoinConfiguredTracks, getPrejoinName } from './functions';
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media';
import { MiddlewareRegistry } from '../base/redux';
import { getAllPrejoinConfiguredTracks } from './functions';
declare var APP: Object;
@@ -51,10 +50,16 @@ MiddlewareRegistry.register(store => next => async action => {
}
case PREJOIN_START_CONFERENCE: {
const { dispatch, getState } = store;
const { getState, dispatch } = store;
const state = getState();
const { userSelectedSkipPrejoin } = state['features/prejoin'];
_syncParticipantName(dispatch, getState);
const tracks = await getAllPrejoinConfiguredTracks(getState());
userSelectedSkipPrejoin && dispatch(updateSettings({
userSelectedSkipPrejoin
}));
const tracks = await getAllPrejoinConfiguredTracks(state);
APP.conference.prejoinStart(tracks);
@@ -70,26 +75,8 @@ MiddlewareRegistry.register(store => next => async action => {
store.dispatch(setPrejoinVideoMuted(Boolean(action.muted)));
break;
}
}
return next(action);
});
/**
* Sets the local participant name if one is present.
*
* @param {Function} dispatch - The redux dispatch function.
* @param {Function} getState - Gets the current state.
* @returns {undefined}
*/
function _syncParticipantName(dispatch, getState) {
const state = getState();
const name = getPrejoinName(state);
name && dispatch(
participantUpdated({
...getLocalParticipant(state),
name
}),
);
}

View File

@@ -6,10 +6,10 @@ import {
ADD_PREJOIN_VIDEO_TRACK,
SET_DEVICE_STATUS,
SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
SET_SKIP_PREJOIN,
SET_PREJOIN_AUDIO_DISABLED,
SET_PREJOIN_AUDIO_MUTED,
SET_PREJOIN_DEVICE_ERRORS,
SET_PREJOIN_NAME,
SET_PREJOIN_PAGE_VISIBILITY,
SET_PREJOIN_VIDEO_DISABLED,
SET_PREJOIN_VIDEO_MUTED
@@ -24,11 +24,11 @@ const DEFAULT_STATE = {
deviceStatusType: 'ok',
showPrejoin: true,
showJoinByPhoneDialog: false,
userSelectedSkipPrejoin: false,
videoTrack: null,
audioTrack: null,
contentSharingTrack: null,
rawError: '',
name: ''
rawError: ''
};
/**
@@ -58,10 +58,10 @@ ReducerRegistry.register(
};
}
case SET_PREJOIN_NAME: {
case SET_SKIP_PREJOIN: {
return {
...state,
name: action.value
userSelectedSkipPrejoin: action.value
};
}

View File

@@ -16,7 +16,7 @@ import {
isPrejoinAudioMuted,
isAudioDisabled,
isPrejoinPageVisible
} from '../../prejoin';
} from '../../prejoin/functions';
import { muteLocal } from '../../remote-video-menu/actions';
declare var APP: Object;

View File

@@ -21,7 +21,7 @@ import {
isPrejoinPageVisible,
isPrejoinVideoDisabled,
isPrejoinVideoMuted
} from '../../prejoin';
} from '../../prejoin/functions';
import UIEvents from '../../../../service/UI/UIEvents';
declare var APP: Object;

View File

@@ -7,7 +7,7 @@ local hex = require "util.hex";
local jwt = require "luajwtjitsi";
local http = require "net.http";
local jid = require "util.jid";
local json = require "cjson";
local json_safe = require "cjson.safe";
local path = require "util.paths";
local sha256 = require "util.hashes".sha256;
local timer = require "util.timer";
@@ -255,7 +255,10 @@ function Util:process_and_verify_token(session)
if self.asapKeyServer and session.auth_token ~= nil then
local dotFirst = session.auth_token:find("%.");
if not dotFirst then return nil, "Invalid token" end
local header = json.decode(basexx.from_url64(session.auth_token:sub(1,dotFirst-1)));
local header, err = json_safe.decode(basexx.from_url64(session.auth_token:sub(1,dotFirst-1)));
if err then
return false, "not-allowed", "bad token format";
end
local kid = header["kid"];
if kid == nil then
return false, "not-allowed", "'kid' claim is missing";
@@ -398,4 +401,4 @@ function Util:verify_room(session, room_address)
end
end
return Util;
return Util;