mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-26 22:17:47 +00:00
Compare commits
6 Commits
4166
...
saghul-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bf82b573c | ||
|
|
b4b4339a1a | ||
|
|
6773aed67f | ||
|
|
d740752522 | ||
|
|
d93b219c7f | ||
|
|
10cd150a07 |
15
config.js
15
config.js
@@ -217,6 +217,21 @@ var config = {
|
||||
// Default value for the channel "last N" attribute. -1 for unlimited.
|
||||
channelLastN: -1,
|
||||
|
||||
// // Options for the recording limit notification.
|
||||
// recordingLimit: {
|
||||
//
|
||||
// // The recording limit in minutes. Note: This number appears in the notification text
|
||||
// // but doesn't enforce the actual recording time limit. This should be configured in
|
||||
// // jibri!
|
||||
// limit: 60,
|
||||
//
|
||||
// // The name of the app with unlimited recordings.
|
||||
// appName: 'Unlimited recordings APP',
|
||||
//
|
||||
// // The URL of the app with unlimited recordings.
|
||||
// appURL: 'https://unlimited.recordings.app.com/'
|
||||
// },
|
||||
|
||||
// Disables or enables RTX (RFC 4588) (defaults to false).
|
||||
// disableRtx: false,
|
||||
|
||||
|
||||
@@ -1,38 +1,45 @@
|
||||
{
|
||||
"en": "Engleză",
|
||||
"af": "Afrikaans",
|
||||
"az": "",
|
||||
"ar": "Arabă",
|
||||
"bg": "Bulgară",
|
||||
"ca": "Catalană",
|
||||
"cs": "Cehă",
|
||||
"da": "Daneză",
|
||||
"de": "Germană",
|
||||
"el": "Greacă",
|
||||
"enGB": "Engleză (Regatul Unit)",
|
||||
"eo": "Esperanto",
|
||||
"es": "Spaniolă",
|
||||
"esUS": "Spaniolă (America Latină)",
|
||||
"et": "Estonă",
|
||||
"eu": "Bască",
|
||||
"fi": "Finlandeză",
|
||||
"fr": "Franceză",
|
||||
"hy": "Armeniana",
|
||||
"frCA": "Franceză (Canada)",
|
||||
"he": "Ebraică",
|
||||
"hr": "Croată",
|
||||
"hu": "Maghiară",
|
||||
"hy": "Armeană",
|
||||
"id": "Indoneziană",
|
||||
"it": "Italiană",
|
||||
"ja": "Japoneză",
|
||||
"ko": "Koreană",
|
||||
"nb": "",
|
||||
"oc": "Occitan",
|
||||
"lt": "Lituaniană",
|
||||
"nl": "Olandeză",
|
||||
"oc": "Occitană",
|
||||
"pl": "Poloneză",
|
||||
"ptBR": "Portugheză (Brazilia)",
|
||||
"ru": "Rusă",
|
||||
"ro": "Română",
|
||||
"sc": "Sardă",
|
||||
"sk": "Slovacă",
|
||||
"sl": "Slovenă",
|
||||
"sv": "Suedeză",
|
||||
"th": "Thailandeză",
|
||||
"tr": "Turcă",
|
||||
"uk": "Ucraineană",
|
||||
"vi": "Vietnameză",
|
||||
"zhCN": "Chineză (China)",
|
||||
"zhTW": "Chineză (Taiwan)",
|
||||
"nl": "Olandeză",
|
||||
"hu": "Maghiară",
|
||||
"hr": "Croată",
|
||||
"frCA": "Franceză (Canadia)",
|
||||
"fi": "Finlandeză",
|
||||
"et": "Estoniană",
|
||||
"esUS": "Spaniolă (America Latină)",
|
||||
"enGB": "Engleză (Regatul Unit)",
|
||||
"da": "Daneză",
|
||||
"că": "Catalană"
|
||||
"zhTW": "Chineză (Taiwan)"
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
"bluetooth": "Bluetooth",
|
||||
"headphones": "Căști",
|
||||
"phone": "Telefon",
|
||||
"speaker": "Difuzor"
|
||||
"speaker": "Difuzor",
|
||||
"none": ""
|
||||
},
|
||||
"audioOnly": {
|
||||
"audioOnly": "Doar audio"
|
||||
@@ -51,7 +52,12 @@
|
||||
"popover": "Alegeți un pseudonim",
|
||||
"title": "Introduceți un pseudonim pentru a conversa"
|
||||
},
|
||||
"title": "Apel video"
|
||||
"title": "Apel video",
|
||||
"you": "",
|
||||
"privateNotice": "",
|
||||
"noMessagesMessage": "",
|
||||
"messageTo": "",
|
||||
"fieldPlaceHolder": ""
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "Sunteți conectat la conversația dumneavoastră ..."
|
||||
@@ -66,7 +72,11 @@
|
||||
"DISCONNECTED": "Deconectat",
|
||||
"DISCONNECTING": "Se deconectează",
|
||||
"ERROR": "Eroare",
|
||||
"RECONNECTING": "A apărut o eroare de rețea. Reconectare..."
|
||||
"RECONNECTING": "A apărut o eroare de rețea. Reconectare...",
|
||||
"LOW_BANDWIDTH": "",
|
||||
"GOT_SESSION_ID": "",
|
||||
"GET_SESSION_ID_ERROR": "",
|
||||
"FETCH_SESSION_ID": ""
|
||||
},
|
||||
"connectionindicator": {
|
||||
"address": "Adresă:",
|
||||
@@ -97,7 +107,8 @@
|
||||
"status": "Conexiune:",
|
||||
"transport": "Mod Transport:",
|
||||
"transport_plural": "Moduri Transport:",
|
||||
"turn": " (turn)"
|
||||
"turn": " (turn)",
|
||||
"e2e_rtt": ""
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "Mai devreme",
|
||||
@@ -109,6 +120,8 @@
|
||||
"description": "Nu s-a întâmplat nimic? Am încercat să vă deschidem conversația în {{app}} aplicația pentru desktop. Încercați din nou sau deschideți {{app}} aplicația web.",
|
||||
"descriptionWithoutWeb": "",
|
||||
"downloadApp": "Descărcați aplicația",
|
||||
"ifDoNotHaveApp": "Dacă nu aveti încă aplicația atunci:",
|
||||
"ifHaveApp": "Dacă aveti deja aplicația:",
|
||||
"launchWebButton": "Deschideți în browser",
|
||||
"openApp": "Continuați spre aplicație",
|
||||
"title": "Deschidere apel video în {{app}}...",
|
||||
@@ -256,13 +269,24 @@
|
||||
"WaitForHostMsgWOk": "Conferința {{room}} nu a început. Daca sunteți moderatorul, apăsați butonul OK pentru autentificare. Dacă nu, așteptați ca moderatorul să înceapă conferința.",
|
||||
"WaitingForHost": "Așteptare moderator conferință ...",
|
||||
"Yes": "Da",
|
||||
"yourEntireScreen": "Întregul ecran"
|
||||
"yourEntireScreen": "Întregul ecran",
|
||||
"sendPrivateMessageTitle": "",
|
||||
"sendPrivateMessageOk": "",
|
||||
"sendPrivateMessageCancel": "",
|
||||
"sendPrivateMessage": "",
|
||||
"screenSharingAudio": "",
|
||||
"muteEveryoneStartMuted": "",
|
||||
"muteEveryoneSelf": "",
|
||||
"muteEveryoneTitle": "",
|
||||
"muteEveryoneDialog": "",
|
||||
"muteEveryoneElseTitle": "",
|
||||
"muteEveryoneElseDialog": ""
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "Este {{status}}"
|
||||
},
|
||||
"feedback": {
|
||||
"average": "Mediu",
|
||||
"average": "Medie",
|
||||
"bad": "Rău",
|
||||
"detailsLabel": "Spuneți-ne mai multe despre experiența dumneavoastră.",
|
||||
"good": "Bine",
|
||||
@@ -335,7 +359,8 @@
|
||||
"toggleFilmstrip": "Afișați sau ascundeți imagini video",
|
||||
"toggleScreensharing": "Comutați între cameră și partajare ecran",
|
||||
"toggleShortcuts": "Arătați sau ascundeți comenzi rapide tastatură",
|
||||
"videoMute": "Porniți sau opriți camera"
|
||||
"videoMute": "Porniți sau opriți camera",
|
||||
"videoQuality": ""
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "Lucrăm la eliberarea resurselor de transmitere. Vă rugam să încercați din nou în câteva minute.",
|
||||
@@ -363,7 +388,11 @@
|
||||
"signOut": "Deconectare",
|
||||
"start": "Începeți o transmitere live",
|
||||
"streamIdHelp": "Ce înseamnă acest lucru?",
|
||||
"unavailableTitle": "Transmitere live indisponibilă"
|
||||
"unavailableTitle": "Transmitere live indisponibilă",
|
||||
"onBy": "",
|
||||
"offBy": "",
|
||||
"googlePrivacyPolicy": "Politica de confidențialitate Google",
|
||||
"youtubeTerms": "Termeni și condiții Youtube"
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -426,9 +455,10 @@
|
||||
"unmute": "",
|
||||
"newDeviceCameraTitle": "A fost detectată o cameră noua",
|
||||
"newDeviceAudioTitle": "A fost detectat un dispozitiv audio nou",
|
||||
"newDeviceAction": "Utilizați"
|
||||
"newDeviceAction": "Utilizați",
|
||||
"suboptimalBrowserWarning": "Folosind acest browser nu veți beneficia de cea mai bună experiență pentru aceste apeluri video. În timp ce lucrăm la asta, vă recomandăm să folosiți unul din <a href='{{recommendedBrowserPageLink}}' target='_blank'>browser-ele suportate</a>."
|
||||
},
|
||||
"passwordSetRemotely": "Setat de un alt membru",
|
||||
"passwordSetRemotely": "Setată de un alt membru",
|
||||
"passwordDigitsOnly": "Până la {{number}} cifre",
|
||||
"poweredby": "cu sprijinul",
|
||||
"presenceStatus": {
|
||||
@@ -474,7 +504,9 @@
|
||||
"signIn": "Conectare",
|
||||
"signOut": "Deconectare",
|
||||
"unavailable": "Oops! Serviciul {{serviceName}} este indisponibil momentan. Se lucrează la remedierea acestei probleme. Vă rugam să încercați mai tărziu.",
|
||||
"unavailableTitle": "Înregistrare indisponibilă"
|
||||
"unavailableTitle": "Înregistrare indisponibilă",
|
||||
"onBy": "{{name}} a pornit înregistrarea",
|
||||
"offBy": "{{name}} a oprit înregistrarea"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Trageți pentru a reîmprospătă"
|
||||
@@ -499,8 +531,10 @@
|
||||
"selectCamera": "Cameră",
|
||||
"selectMic": "Microfon",
|
||||
"startAudioMuted": "Toată lumea începe cu sunetul dezactivat",
|
||||
"startVideoMuted": "Toată lumea începe cu sunetul dezactivat",
|
||||
"title": "Setări"
|
||||
"startVideoMuted": "Toată lumea începe cu fară video",
|
||||
"title": "Setări",
|
||||
"speakers": "Difuzoare",
|
||||
"microphones": "Microfoane"
|
||||
},
|
||||
"settingsView": {
|
||||
"alertOk": "OK",
|
||||
@@ -515,7 +549,11 @@
|
||||
"serverURL": "Server URL",
|
||||
"startWithAudioMuted": "Începeți cu sunetul dezactivat",
|
||||
"startWithVideoMuted": "Începeți cu video dezactivat",
|
||||
"version": "Version"
|
||||
"version": "Version",
|
||||
"showAdvanced": "Arată setările avansate",
|
||||
"disableP2P": "Dezactivează modul Peer-To-Peer",
|
||||
"disableCallIntegration": "Dezactivează integrarea cu apelurile native",
|
||||
"advanced": "Avansat"
|
||||
},
|
||||
"share": {
|
||||
"dialInfoText": "\n\n=====\n\nVreti doar să apelați telefonic?\n\n{{defaultDialInNumber}}Faceți click pe acest link pentru a vizualiza numerele pentru apelare în acest apel video\n{{dialInfoPageUrl}}",
|
||||
@@ -525,10 +563,10 @@
|
||||
"speakerStats": {
|
||||
"hours": "{{count}}h",
|
||||
"minutes": "{{count}}m",
|
||||
"name": "Name",
|
||||
"name": "Nume",
|
||||
"seconds": "{{count}}s",
|
||||
"speakerStats": "Parametrii difuzorului",
|
||||
"speakerTime": "Durată participant"
|
||||
"speakerStats": "Statistici participanți",
|
||||
"speakerTime": "Durată vorbire participant"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
@@ -572,9 +610,14 @@
|
||||
"tileView": "Afișați/ascundeți miniatura video",
|
||||
"toggleCamera": "Afișați / ascundeți camera",
|
||||
"videomute": "Activați / dezactivați înregistrarea",
|
||||
"videoblur": ""
|
||||
"videoblur": "",
|
||||
"privateMessage": "Trimite un mesaj privat",
|
||||
"muteEveryone": "Oprește microfonul tuturor",
|
||||
"moreOptions": "Arată mai multe opțiuni",
|
||||
"help": "Ajutor",
|
||||
"download": "Descarcă aplicațiile noastre"
|
||||
},
|
||||
"addPeople": "Adaugați persoane în apel",
|
||||
"addPeople": "Adăugați persoane în apel",
|
||||
"audioOnlyOff": "Dezactivați modul 'doar audio'",
|
||||
"audioOnlyOn": "Activați modul 'doar audio'",
|
||||
"audioRoute": "Selectați dispozitivul pentru sunet",
|
||||
@@ -664,9 +707,6 @@
|
||||
"lowDefinition": "Calitate redusă",
|
||||
"onlyAudioAvailable": "Doar audio este disponibil",
|
||||
"onlyAudioSupported": "În acest navigator este suportat doar 'mod audio'.",
|
||||
"p2pEnabled": "Peer-to-Peer activat",
|
||||
"p2pVideoQualityDescription": "În modul peer-to-peer pentru calitatea apelurilor primite puteți alege doar între calitate superioară sau 'mod audio'. Alte setări nu pot fi activate până când modul peer-to-peer nu este dezactivat.",
|
||||
"recHighDefinitionOnly": "Este de preferat o calitate superioară.",
|
||||
"sd": "SD",
|
||||
"standardDefinition": "Calitate standard"
|
||||
},
|
||||
@@ -698,7 +738,7 @@
|
||||
"go": "ÎNCEPEȚI",
|
||||
"join": "ACCESARE",
|
||||
"info": "Informații",
|
||||
"privacy": "Securitate",
|
||||
"privacy": "Confidențialitate",
|
||||
"recentList": "Recent",
|
||||
"recentListDelete": "Ștergeți",
|
||||
"recentListEmpty": "Lista dumneavoastră recentă este momentan goală. Discutați cu echipa dumneavoastră și veți găsi toate conversațiile aici.",
|
||||
@@ -707,13 +747,16 @@
|
||||
"roomnameHint": "Introduceți numele sau adresa web a ședinței la care doriți să vă conectați. Puteți asocia un nume, dar transmiteți și celorlalți participanți acest nume.",
|
||||
"sendFeedback": "Lăsați-ne feedback",
|
||||
"terms": "Termeni",
|
||||
"title": "Video-conferință securizata, cu opțiuni multiple și complet gratuită "
|
||||
"title": "Video-conferință securizată, cu multiple funcționalități și complet gratuită"
|
||||
},
|
||||
"lonelyMeetingExperience": {
|
||||
"button": "Invită alte persoane",
|
||||
"youAreAlone": "Ești singura persoană din acest apel"
|
||||
"documentSharing": {
|
||||
"title": ""
|
||||
},
|
||||
"helpView": {
|
||||
"header": "Centru de ajutor"
|
||||
}
|
||||
"defaultNickname": "",
|
||||
"chromeExtensionBanner": {
|
||||
"dontShowAgain": "",
|
||||
"buttonText": "",
|
||||
"installExtensionText": ""
|
||||
},
|
||||
"raisedHand": "Ar dori să vorbească"
|
||||
}
|
||||
|
||||
@@ -395,6 +395,8 @@
|
||||
"videoQuality": "Manage call quality"
|
||||
},
|
||||
"liveStreaming": {
|
||||
"limitNotificationDescriptionWeb": "Due to high demand your streaming will be limited to {{limit}} min. For unlimited streaming try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
"limitNotificationDescriptionNative": "Your streaming will be limited to {{limit}} min. For unlimited streaming try {{app}}.",
|
||||
"busy": "We're working on freeing streaming resources. Please try again in a few minutes.",
|
||||
"busyTitle": "All streamers are currently busy",
|
||||
"changeSignIn": "Switch accounts.",
|
||||
@@ -552,6 +554,8 @@
|
||||
},
|
||||
"raisedHand": "Would like to speak",
|
||||
"recording": {
|
||||
"limitNotificationDescriptionWeb": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
"limitNotificationDescriptionNative": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try <3>{{app}}</3>.",
|
||||
"authDropboxText": "Upload to Dropbox",
|
||||
"availableSpace": "Available space: {{spaceLeft}} MB (approximately {{duration}} minutes of recording)",
|
||||
"beta": "BETA",
|
||||
|
||||
@@ -8,16 +8,21 @@ import {
|
||||
sendAnalytics
|
||||
} from '../../react/features/analytics';
|
||||
import {
|
||||
getCurrentConference,
|
||||
sendTones,
|
||||
setPassword,
|
||||
setSubject
|
||||
} from '../../react/features/base/conference';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
|
||||
import { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
|
||||
import {
|
||||
processExternalDeviceRequest
|
||||
} from '../../react/features/device-selection/functions';
|
||||
import { isEnabled as isDropboxEnabled } from '../../react/features/dropbox';
|
||||
import { setE2EEKey } from '../../react/features/e2ee';
|
||||
import { invite } from '../../react/features/invite';
|
||||
import { RECORDING_TYPES } from '../../react/features/recording/constants';
|
||||
import { getActiveSession } from '../../react/features/recording/functions';
|
||||
import { muteAllParticipants } from '../../react/features/remote-video-menu/actions';
|
||||
import { toggleTileView } from '../../react/features/video-layout';
|
||||
import { setVideoQuality } from '../../react/features/video-quality';
|
||||
@@ -190,6 +195,114 @@ function initCommands() {
|
||||
logger.debug('Set video quality command received');
|
||||
sendAnalytics(createApiEvent('set.video.quality'));
|
||||
APP.store.dispatch(setVideoQuality(frameHeight));
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts a file recording or streaming depending on the passed on params.
|
||||
* For youtube streams, `youtubeStreamKey` must be passed on. `youtubeBroadcastID` is optional.
|
||||
* For dropbox recording, recording `mode` should be `file` and a dropbox oauth2 token must be provided.
|
||||
* For file recording, recording `mode` should be `file` and optionally `shouldShare` could be passed on.
|
||||
* No other params should be passed.
|
||||
*
|
||||
* @param { string } arg.mode - Recording mode, either `file` or `stream`.
|
||||
* @param { string } arg.dropboxToken - Dropbox oauth2 token.
|
||||
* @param { boolean } arg.shouldShare - Whether the recording should be shared with the participants or not.
|
||||
* Only applies to certain jitsi meet deploys.
|
||||
* @param { string } arg.youtubeStreamKey - The youtube stream key.
|
||||
* @param { string } arg.youtubeBroadcastID - The youtube broacast ID.
|
||||
* @returns {void}
|
||||
*/
|
||||
'start-recording': ({ mode, dropboxToken, shouldShare, youtubeStreamKey, youtubeBroadcastID }) => {
|
||||
const state = APP.store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
if (!conference) {
|
||||
logger.error('Conference is not defined');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (dropboxToken && !isDropboxEnabled(state)) {
|
||||
logger.error('Failed starting recording: dropbox is not enabled on this deployment');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === JitsiRecordingConstants.mode.STREAM && !youtubeStreamKey) {
|
||||
logger.error('Failed starting recording: missing youtube stream key');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let recordingConfig;
|
||||
|
||||
if (mode === JitsiRecordingConstants.mode.FILE) {
|
||||
if (dropboxToken) {
|
||||
recordingConfig = {
|
||||
mode: JitsiRecordingConstants.mode.FILE,
|
||||
appData: JSON.stringify({
|
||||
'file_recording_metadata': {
|
||||
'upload_credentials': {
|
||||
'service_name': RECORDING_TYPES.DROPBOX,
|
||||
'token': dropboxToken
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
} else {
|
||||
recordingConfig = {
|
||||
mode: JitsiRecordingConstants.mode.FILE,
|
||||
appData: JSON.stringify({
|
||||
'file_recording_metadata': {
|
||||
'share': shouldShare
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
} else if (mode === JitsiRecordingConstants.mode.STREAM) {
|
||||
recordingConfig = {
|
||||
broadcastId: youtubeBroadcastID,
|
||||
mode: JitsiRecordingConstants.mode.STREAM,
|
||||
streamId: youtubeStreamKey
|
||||
};
|
||||
} else {
|
||||
logger.error('Invalid recording mode provided');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
conference.startRecording(recordingConfig);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops a recording or streaming in progress.
|
||||
*
|
||||
* @param {string} mode - `file` or `stream`.
|
||||
* @returns {void}
|
||||
*/
|
||||
'stop-recording': mode => {
|
||||
const state = APP.store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
if (!conference) {
|
||||
logger.error('Conference is not defined');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (![ JitsiRecordingConstants.mode.FILE, JitsiRecordingConstants.mode.STREAM ].includes(mode)) {
|
||||
logger.error('Invalid recording mode provided!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const activeSession = getActiveSession(state, mode);
|
||||
|
||||
if (activeSession && activeSession.id) {
|
||||
conference.stopRecording(activeSession.id);
|
||||
} else {
|
||||
logger.error('No recording or streaming session found');
|
||||
}
|
||||
}
|
||||
};
|
||||
transport.on('event', ({ data, name }) => {
|
||||
@@ -514,7 +627,8 @@ class API {
|
||||
notifyDeviceListChanged(devices: Object) {
|
||||
this._sendEvent({
|
||||
name: 'device-list-changed',
|
||||
devices });
|
||||
devices
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
2
modules/API/external/external_api.js
vendored
2
modules/API/external/external_api.js
vendored
@@ -37,6 +37,8 @@ const commands = {
|
||||
sendEndpointTextMessage: 'send-endpoint-text-message',
|
||||
sendTones: 'send-tones',
|
||||
setVideoQuality: 'set-video-quality',
|
||||
startRecording: 'start-recording',
|
||||
stopRecording: 'stop-recording',
|
||||
subject: 'subject',
|
||||
submitFeedback: 'submit-feedback',
|
||||
toggleAudio: 'toggle-audio',
|
||||
|
||||
@@ -31,7 +31,7 @@ export const SHARED_VIDEO_CONTAINER_TYPE = 'sharedvideo';
|
||||
* Example shared video link.
|
||||
* @type {string}
|
||||
*/
|
||||
const defaultSharedVideoLink = 'https://www.youtube.com/watch?v=xNXN7CZk8X0';
|
||||
const defaultSharedVideoLink = 'https://youtu.be/TB7LlM4erx8';
|
||||
const updateInterval = 5000; // milliseconds
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,7 @@ import { DialogContainer } from '../../base/dialog';
|
||||
import { CALL_INTEGRATION_ENABLED, updateFlags } from '../../base/flags';
|
||||
import '../../base/jwt';
|
||||
import { Platform } from '../../base/react';
|
||||
import '../../base/responsive-ui';
|
||||
import { DimensionsDetector, clientResized } from '../../base/responsive-ui';
|
||||
import { updateSettings } from '../../base/settings';
|
||||
import '../../google-api';
|
||||
import '../../mobile/audio-mode';
|
||||
@@ -78,6 +78,9 @@ export class App extends AbstractApp {
|
||||
// This will effectively kill the app. In accord with the Web, do not
|
||||
// kill the app.
|
||||
this._maybeDisableExceptionsManager();
|
||||
|
||||
// Bind event handler so it is only bound once per instance.
|
||||
this._onDimensionsChanged = this._onDimensionsChanged.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,6 +110,21 @@ export class App extends AbstractApp {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the parent method to inject {@link DimensionsDetector} as
|
||||
* the top most component.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_createMainElement(component, props) {
|
||||
return (
|
||||
<DimensionsDetector
|
||||
onDimensionsChanged = { this._onDimensionsChanged }>
|
||||
{ super._createMainElement(component, props) }
|
||||
</DimensionsDetector>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to disable the use of React Native
|
||||
* {@link ExceptionsManager#handleException} on platforms and in
|
||||
@@ -144,6 +162,22 @@ export class App extends AbstractApp {
|
||||
}
|
||||
}
|
||||
|
||||
_onDimensionsChanged: (width: number, height: number) => void;
|
||||
|
||||
/**
|
||||
* Updates the known available size for the app to occupy.
|
||||
*
|
||||
* @param {number} width - The component's current width.
|
||||
* @param {number} height - The component's current height.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDimensionsChanged(width: number, height: number) {
|
||||
const { dispatch } = this.state.store;
|
||||
|
||||
dispatch(clientResized(width, height));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific dialog container.
|
||||
*
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { connect } from '../../redux';
|
||||
import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from '../constants';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link AspectRatioAware}.
|
||||
*/
|
||||
type Props = {
|
||||
aspectRatio: ASPECT_RATIO_NARROW | ASPECT_RATIO_WIDE
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether a specific React {@code Component} decorated into an
|
||||
* {@link AspectRatioAware} has {@link ASPECT_RATIO_NARROW} as the value of its
|
||||
* {@code aspectRatio} React prop.
|
||||
*
|
||||
* @param {AspectRatioAware} component - An {@link AspectRatioAware} which may
|
||||
* have an {@code aspectRatio} React prop.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isNarrowAspectRatio(component: React$Component<*>) {
|
||||
return component.props.aspectRatio === ASPECT_RATIO_NARROW;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates a specific React {@code Component} class into an
|
||||
* {@link AspectRatioAware} which provides the React prop {@code aspectRatio}
|
||||
* updated on each redux state change.
|
||||
*
|
||||
* @param {Class<React$Component>} WrappedComponent - A React {@code Component}
|
||||
* class to be wrapped.
|
||||
* @returns {AspectRatioAwareWrapper}
|
||||
*/
|
||||
export function makeAspectRatioAware(
|
||||
WrappedComponent: Class<React$Component<*>>
|
||||
): Class<React$Component<*>> {
|
||||
/**
|
||||
* Renders {@code WrappedComponent} with the React prop {@code aspectRatio}.
|
||||
*/
|
||||
class AspectRatioAware extends Component<Props> {
|
||||
/**
|
||||
* Implement's React render method to wrap the nested component.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
render(): React$Element<*> {
|
||||
return <WrappedComponent { ...this.props } />;
|
||||
}
|
||||
}
|
||||
|
||||
return connect(_mapStateToProps)(AspectRatioAware);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to {@link AspectRatioAware} props.
|
||||
*
|
||||
* @param {Object} state - The whole redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* aspectRatio: Symbol
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
aspectRatio: state['features/base/responsive-ui'].aspectRatio
|
||||
};
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* AspectRatioDetector component's property types.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
@@ -64,7 +60,7 @@ export default class DimensionsDetector extends PureComponent<Props> {
|
||||
return (
|
||||
<View
|
||||
onLayout = { this._onLayout }
|
||||
style = { styles.dimensionsDetector } >
|
||||
style = { StyleSheet.absoluteFillObject } >
|
||||
{ this.props.children }
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from './AspectRatioAware';
|
||||
export { default as DimensionsDetector } from './DimensionsDetector';
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { createStyleSheet } from '../../styles';
|
||||
|
||||
/**
|
||||
* The styles of the feature base/responsive-ui.
|
||||
*/
|
||||
export default createStyleSheet({
|
||||
/**
|
||||
* The style of {@link DimensionsDetector} used on react-native.
|
||||
*/
|
||||
dimensionsDetector: {
|
||||
alignSelf: 'stretch',
|
||||
flex: 1
|
||||
}
|
||||
});
|
||||
@@ -1,16 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import { Dimensions } from 'react-native';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app';
|
||||
import { MiddlewareRegistry } from '../../base/redux';
|
||||
|
||||
import { CLIENT_RESIZED } from './actionTypes';
|
||||
import { setAspectRatio, setReducedUI } from './actions';
|
||||
|
||||
/**
|
||||
* Dimensions change handler.
|
||||
*/
|
||||
let handler;
|
||||
|
||||
/**
|
||||
* Middleware that handles widnow dimension changes and updates the aspect ratio and
|
||||
@@ -19,65 +13,19 @@ let handler;
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
MiddlewareRegistry.register(({ dispatch }) => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
case APP_WILL_UNMOUNT: {
|
||||
_appWillUnmount();
|
||||
case CLIENT_RESIZED: {
|
||||
const { clientWidth: width, clientHeight: height } = action;
|
||||
|
||||
dispatch(setAspectRatio(width, height));
|
||||
dispatch(setReducedUI(width, height));
|
||||
break;
|
||||
}
|
||||
case APP_WILL_MOUNT:
|
||||
_appWillMount(store);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Notifies this feature that the action {@link APP_WILL_MOUNT} is being
|
||||
* dispatched within a specific redux {@code store}.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _appWillMount(store) {
|
||||
handler = dim => {
|
||||
_onDimensionsChange(dim, store);
|
||||
};
|
||||
|
||||
Dimensions.addEventListener('change', handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this feature that the action {@link APP_WILL_UNMOUNT} is being
|
||||
* dispatched within a specific redux {@code store}.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _appWillUnmount() {
|
||||
Dimensions.removeEventListener('change', handler);
|
||||
|
||||
handler = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles window dimension changes.
|
||||
*
|
||||
* @param {Object} dimensions - The new dimensions.
|
||||
* @param {Store} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onDimensionsChange(dimensions, store) {
|
||||
const { width, height } = dimensions.window;
|
||||
const { dispatch } = store;
|
||||
|
||||
dispatch(setAspectRatio(width, height));
|
||||
dispatch(setReducedUI(width, height));
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ const DEFAULT_STATE = {
|
||||
ReducerRegistry.register('features/base/responsive-ui', (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case CLIENT_RESIZED: {
|
||||
|
||||
return {
|
||||
...state,
|
||||
clientWidth: action.clientWidth,
|
||||
|
||||
@@ -8,10 +8,7 @@ import { appNavigate } from '../../../app';
|
||||
import { PIP_ENABLED, getFeatureFlag } from '../../../base/flags';
|
||||
import { Container, LoadingIndicator, TintedView } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import {
|
||||
isNarrowAspectRatio,
|
||||
makeAspectRatioAware
|
||||
} from '../../../base/responsive-ui';
|
||||
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
||||
import { TestConnectionInfo } from '../../../base/testing';
|
||||
import { ConferenceNotification, isCalendarEnabled } from '../../../calendar-sync';
|
||||
import { Chat } from '../../../chat';
|
||||
@@ -45,10 +42,13 @@ import styles, { NAVBAR_GRADIENT_COLORS } from './styles';
|
||||
*/
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* Application's aspect ratio.
|
||||
*/
|
||||
_aspectRatio: Symbol,
|
||||
|
||||
/**
|
||||
* Wherther the calendar feature is enabled or not.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_calendarEnabled: boolean,
|
||||
|
||||
@@ -57,15 +57,11 @@ type Props = AbstractProps & {
|
||||
* conference which includes establishing the XMPP connection and then
|
||||
* joining the room. If truthy, then an activity/loading indicator will be
|
||||
* rendered.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_connecting: boolean,
|
||||
|
||||
/**
|
||||
* Set to {@code true} when the filmstrip is currently visible.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_filmstripVisible: boolean,
|
||||
|
||||
@@ -76,34 +72,17 @@ type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is enabled.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_pictureInPictureEnabled: boolean,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the UI is reduced (to accommodate
|
||||
* smaller display areas).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_reducedUI: boolean,
|
||||
|
||||
/**
|
||||
* The handler which dispatches the (redux) action {@link setToolboxVisible}
|
||||
* to show/hide the {@link Toolbox}.
|
||||
*
|
||||
* @param {boolean} visible - {@code true} to show the {@code Toolbox} or
|
||||
* {@code false} to hide it.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setToolboxVisible: Function,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the Toolbox is visible.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_toolboxVisible: boolean,
|
||||
|
||||
@@ -249,6 +228,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
*/
|
||||
_renderContent() {
|
||||
const {
|
||||
_aspectRatio,
|
||||
_connecting,
|
||||
_filmstripVisible,
|
||||
_largeVideoParticipantId,
|
||||
@@ -257,7 +237,8 @@ class Conference extends AbstractConference<Props, *> {
|
||||
_toolboxVisible
|
||||
} = this.props;
|
||||
const showGradient = _toolboxVisible;
|
||||
const applyGradientStretching = _filmstripVisible && isNarrowAspectRatio(this) && !_shouldDisplayTileView;
|
||||
const applyGradientStretching
|
||||
= _filmstripVisible && _aspectRatio === ASPECT_RATIO_NARROW && !_shouldDisplayTileView;
|
||||
|
||||
if (_reducedUI) {
|
||||
return this._renderContentForReducedUi();
|
||||
@@ -393,7 +374,9 @@ class Conference extends AbstractConference<Props, *> {
|
||||
// flex layout. The only option that seemed to limit the notification's
|
||||
// size was explicit 'width' value which is not better than the margin
|
||||
// added here.
|
||||
if (this.props._filmstripVisible && !isNarrowAspectRatio(this)) {
|
||||
const { _aspectRatio, _filmstripVisible } = this.props;
|
||||
|
||||
if (_filmstripVisible && _aspectRatio !== ASPECT_RATIO_NARROW) {
|
||||
notificationsStyle.marginRight = FILMSTRIP_SIZE;
|
||||
}
|
||||
|
||||
@@ -433,7 +416,7 @@ function _mapStateToProps(state) {
|
||||
joining,
|
||||
leaving
|
||||
} = state['features/base/conference'];
|
||||
const { reducedUI } = state['features/base/responsive-ui'];
|
||||
const { aspectRatio, reducedUI } = state['features/base/responsive-ui'];
|
||||
|
||||
// XXX There is a window of time between the successful establishment of the
|
||||
// XMPP connection and the subsequent commencement of joining the MUC during
|
||||
@@ -449,61 +432,15 @@ function _mapStateToProps(state) {
|
||||
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
|
||||
/**
|
||||
* Wherther the calendar feature is enabled or not.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_aspectRatio: aspectRatio,
|
||||
_calendarEnabled: isCalendarEnabled(state),
|
||||
|
||||
/**
|
||||
* The indicator which determines that we are still connecting to the
|
||||
* conference which includes establishing the XMPP connection and then
|
||||
* joining the room. If truthy, then an activity/loading indicator will
|
||||
* be rendered.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_connecting: Boolean(connecting_),
|
||||
|
||||
/**
|
||||
* Is {@code true} when the filmstrip is currently visible.
|
||||
*/
|
||||
_filmstripVisible: isFilmstripVisible(state),
|
||||
|
||||
/**
|
||||
* The ID of the participant currently on stage.
|
||||
*/
|
||||
_largeVideoParticipantId: state['features/large-video'].participantId,
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is enabled.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the UI is reduced (to
|
||||
* accommodate smaller display areas).
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_reducedUI: reducedUI,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the Toolbox is visible.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_toolboxVisible: isToolboxVisible(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(makeAspectRatioAware(Conference));
|
||||
export default connect(_mapStateToProps)(Conference);
|
||||
|
||||
@@ -5,10 +5,7 @@ import { TouchableOpacity, View } from 'react-native';
|
||||
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import { connect } from '../../../base/redux';
|
||||
import {
|
||||
isNarrowAspectRatio,
|
||||
makeAspectRatioAware
|
||||
} from '../../../base/responsive-ui';
|
||||
import { ASPECT_RATIO_WIDE } from '../../../base/responsive-ui/constants';
|
||||
import {
|
||||
RecordingExpandedLabel
|
||||
} from '../../../recording';
|
||||
@@ -29,14 +26,19 @@ import styles from './styles';
|
||||
type Props = AbstractLabelsProps & {
|
||||
|
||||
/**
|
||||
* Function to translate i18n labels.
|
||||
* Application's aspect ratio.
|
||||
*/
|
||||
t: Function,
|
||||
_aspectRatio: Symbol,
|
||||
|
||||
/**
|
||||
* True if the labels should be visible, false otherwise.
|
||||
*/
|
||||
_visible: boolean
|
||||
_visible: boolean,
|
||||
|
||||
/**
|
||||
* Function to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -149,12 +151,13 @@ class Labels extends AbstractLabels<Props, State> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
if (!this.props._visible) {
|
||||
const { _aspectRatio, _filmstripVisible, _visible } = this.props;
|
||||
|
||||
if (!_visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const wide = !isNarrowAspectRatio(this);
|
||||
const { _filmstripVisible } = this.props;
|
||||
const wide = _aspectRatio === ASPECT_RATIO_WIDE;
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -354,8 +357,9 @@ class Labels extends AbstractLabels<Props, State> {
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
..._abstractMapStateToProps(state),
|
||||
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
|
||||
_visible: !shouldDisplayNotifications(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(makeAspectRatioAware(Labels));
|
||||
export default connect(_mapStateToProps)(Labels);
|
||||
|
||||
@@ -41,8 +41,7 @@ class Labels extends AbstractLabels<Props, State> {
|
||||
*/
|
||||
static getDerivedStateFromProps(props: Props, prevState: State) {
|
||||
return {
|
||||
filmstripBecomingVisible: !prevState.filmstripBecomingVisible
|
||||
&& props._filmstripVisible
|
||||
filmstripBecomingVisible: !prevState.filmstripBecomingVisible && props._filmstripVisible
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,7 @@ import { ScrollView } from 'react-native';
|
||||
|
||||
import { Container, Platform } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import {
|
||||
isNarrowAspectRatio,
|
||||
makeAspectRatioAware
|
||||
} from '../../../base/responsive-ui';
|
||||
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
||||
import { isFilmstripVisible } from '../../functions';
|
||||
|
||||
import LocalThumbnail from './LocalThumbnail';
|
||||
@@ -20,24 +17,23 @@ import styles from './styles';
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Application's aspect ratio.
|
||||
*/
|
||||
_aspectRatio: Symbol,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the filmstrip is enabled.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_enabled: boolean,
|
||||
|
||||
/**
|
||||
* The participants in the conference.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_participants: Array<any>,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the filmstrip is visible.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_visible: boolean
|
||||
};
|
||||
@@ -90,40 +86,36 @@ class Filmstrip extends Component<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
if (!this.props._enabled) {
|
||||
const { _aspectRatio, _enabled, _participants, _visible } = this.props;
|
||||
|
||||
if (!_enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isNarrowAspectRatio_ = isNarrowAspectRatio(this);
|
||||
const filmstripStyle
|
||||
= isNarrowAspectRatio_
|
||||
? styles.filmstripNarrow
|
||||
: styles.filmstripWide;
|
||||
const isNarrowAspectRatio = _aspectRatio === ASPECT_RATIO_NARROW;
|
||||
const filmstripStyle = isNarrowAspectRatio ? styles.filmstripNarrow : styles.filmstripWide;
|
||||
|
||||
return (
|
||||
<Container
|
||||
style = { filmstripStyle }
|
||||
visible = { this.props._visible }>
|
||||
visible = { _visible }>
|
||||
{
|
||||
this._separateLocalThumbnail
|
||||
&& !isNarrowAspectRatio_
|
||||
&& !isNarrowAspectRatio
|
||||
&& <LocalThumbnail />
|
||||
}
|
||||
<ScrollView
|
||||
horizontal = { isNarrowAspectRatio_ }
|
||||
horizontal = { isNarrowAspectRatio }
|
||||
showsHorizontalScrollIndicator = { false }
|
||||
showsVerticalScrollIndicator = { false }
|
||||
style = { styles.scrollView } >
|
||||
{
|
||||
!this._separateLocalThumbnail
|
||||
&& !isNarrowAspectRatio_
|
||||
!this._separateLocalThumbnail && !isNarrowAspectRatio
|
||||
&& <LocalThumbnail />
|
||||
}
|
||||
{
|
||||
|
||||
this._sort(
|
||||
this.props._participants,
|
||||
isNarrowAspectRatio_)
|
||||
this._sort(_participants, isNarrowAspectRatio)
|
||||
.map(p => (
|
||||
<Thumbnail
|
||||
key = { p.id }
|
||||
@@ -131,14 +123,12 @@ class Filmstrip extends Component<Props> {
|
||||
|
||||
}
|
||||
{
|
||||
!this._separateLocalThumbnail
|
||||
&& isNarrowAspectRatio_
|
||||
!this._separateLocalThumbnail && isNarrowAspectRatio
|
||||
&& <LocalThumbnail />
|
||||
}
|
||||
</ScrollView>
|
||||
{
|
||||
this._separateLocalThumbnail
|
||||
&& isNarrowAspectRatio_
|
||||
this._separateLocalThumbnail && isNarrowAspectRatio
|
||||
&& <LocalThumbnail />
|
||||
}
|
||||
</Container>
|
||||
@@ -150,13 +140,13 @@ class Filmstrip extends Component<Props> {
|
||||
*
|
||||
* @param {Participant[]} participants - The array of {@code Participant}s
|
||||
* to sort in display order.
|
||||
* @param {boolean} isNarrowAspectRatio_ - Indicates if the aspect ratio is
|
||||
* @param {boolean} isNarrowAspectRatio - Indicates if the aspect ratio is
|
||||
* wide or narrow.
|
||||
* @private
|
||||
* @returns {Participant[]} A new array containing the elements of the
|
||||
* specified {@code participants} array sorted in display order.
|
||||
*/
|
||||
_sort(participants, isNarrowAspectRatio_) {
|
||||
_sort(participants, isNarrowAspectRatio) {
|
||||
// XXX Array.prototype.sort() is not appropriate because (1) it operates
|
||||
// in place and (2) it is not necessarily stable.
|
||||
|
||||
@@ -164,7 +154,7 @@ class Filmstrip extends Component<Props> {
|
||||
...participants
|
||||
];
|
||||
|
||||
if (isNarrowAspectRatio_) {
|
||||
if (isNarrowAspectRatio) {
|
||||
// When the narrow aspect ratio is used, we want to have the remote
|
||||
// participants from right to left with the newest added/joined to
|
||||
// the leftmost side. The local participant is the leftmost item.
|
||||
@@ -180,42 +170,18 @@ class Filmstrip extends Component<Props> {
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _participants: Participant[],
|
||||
* _visible: boolean
|
||||
* }}
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const participants = state['features/base/participants'];
|
||||
const { enabled } = state['features/filmstrip'];
|
||||
|
||||
return {
|
||||
/**
|
||||
* The indicator which determines whether the filmstrip is enabled.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
|
||||
_enabled: enabled,
|
||||
|
||||
/**
|
||||
* The remote participants in the conference.
|
||||
*
|
||||
* @private
|
||||
* @type {Participant[]}
|
||||
*/
|
||||
_participants: participants.filter(p => !p.local),
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the filmstrip is visible. The
|
||||
* mobile/react-native Filmstrip is visible when there are at least 2
|
||||
* participants in the conference (including the local one).
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_visible: isFilmstripVisible(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(makeAspectRatioAware(Filmstrip));
|
||||
export default connect(_mapStateToProps)(Filmstrip);
|
||||
|
||||
@@ -13,11 +13,7 @@ import {
|
||||
setMaxReceiverVideoQuality
|
||||
} from '../../../base/conference';
|
||||
import { connect } from '../../../base/redux';
|
||||
import {
|
||||
DimensionsDetector,
|
||||
isNarrowAspectRatio,
|
||||
makeAspectRatioAware
|
||||
} from '../../../base/responsive-ui';
|
||||
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
||||
|
||||
import Thumbnail from './Thumbnail';
|
||||
import styles from './styles';
|
||||
@@ -27,11 +23,26 @@ import styles from './styles';
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Application's aspect ratio.
|
||||
*/
|
||||
_aspectRatio: Symbol,
|
||||
|
||||
/**
|
||||
* Application's viewport height.
|
||||
*/
|
||||
_height: number,
|
||||
|
||||
/**
|
||||
* The participants in the conference.
|
||||
*/
|
||||
_participants: Array<Object>,
|
||||
|
||||
/**
|
||||
* Application's viewport height.
|
||||
*/
|
||||
_width: number,
|
||||
|
||||
/**
|
||||
* Invoked to update the receiver video quality.
|
||||
*/
|
||||
@@ -43,22 +54,6 @@ type Props = {
|
||||
onClick: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@link Component} state of {@link TileView}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The available width for {@link TileView} to occupy.
|
||||
*/
|
||||
height: number,
|
||||
|
||||
/**
|
||||
* The available height for {@link TileView} to occupy.
|
||||
*/
|
||||
width: number
|
||||
};
|
||||
|
||||
/**
|
||||
* The margin for each side of the tile view. Taken away from the available
|
||||
* height and width for the tile container to display in.
|
||||
@@ -82,25 +77,7 @@ const TILE_ASPECT_RATIO = 1;
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class TileView extends Component<Props, State> {
|
||||
state = {
|
||||
height: 0,
|
||||
width: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code TileView} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handler so it is only bound once per instance.
|
||||
this._onDimensionsChanged = this._onDimensionsChanged.bind(this);
|
||||
}
|
||||
|
||||
class TileView extends Component<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#componentDidMount}.
|
||||
*
|
||||
@@ -126,32 +103,27 @@ class TileView extends Component<Props, State> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { onClick } = this.props;
|
||||
const { height, width } = this.state;
|
||||
const rowElements = this._groupIntoRows(
|
||||
this._renderThumbnails(), this._getColumnCount());
|
||||
const { _height, _width, onClick } = this.props;
|
||||
const rowElements = this._groupIntoRows(this._renderThumbnails(), this._getColumnCount());
|
||||
|
||||
return (
|
||||
<DimensionsDetector
|
||||
onDimensionsChanged = { this._onDimensionsChanged }>
|
||||
<ScrollView
|
||||
style = {{
|
||||
...styles.tileView,
|
||||
height,
|
||||
width
|
||||
}}>
|
||||
<TouchableWithoutFeedback onPress = { onClick }>
|
||||
<View
|
||||
style = {{
|
||||
...styles.tileViewRows,
|
||||
minHeight: height,
|
||||
minWidth: width
|
||||
}}>
|
||||
{ rowElements }
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</ScrollView>
|
||||
</DimensionsDetector>
|
||||
<ScrollView
|
||||
style = {{
|
||||
...styles.tileView,
|
||||
height: _height,
|
||||
width: _width
|
||||
}}>
|
||||
<TouchableWithoutFeedback onPress = { onClick }>
|
||||
<View
|
||||
style = {{
|
||||
...styles.tileViewRows,
|
||||
minHeight: _height,
|
||||
minWidth: _width
|
||||
}}>
|
||||
{ rowElements }
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -167,7 +139,7 @@ class TileView extends Component<Props, State> {
|
||||
// For narrow view, tiles should stack on top of each other for a lonely
|
||||
// call and a 1:1 call. Otherwise tiles should be grouped into rows of
|
||||
// two.
|
||||
if (isNarrowAspectRatio(this)) {
|
||||
if (this.props._aspectRatio === ASPECT_RATIO_NARROW) {
|
||||
return participantCount >= 3 ? 2 : 1;
|
||||
}
|
||||
|
||||
@@ -209,19 +181,17 @@ class TileView extends Component<Props, State> {
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getTileDimensions() {
|
||||
const { _participants } = this.props;
|
||||
const { height, width } = this.state;
|
||||
const { _height, _participants, _width } = this.props;
|
||||
const columns = this._getColumnCount();
|
||||
const participantCount = _participants.length;
|
||||
const heightToUse = height - (MARGIN * 2);
|
||||
const widthToUse = width - (MARGIN * 2);
|
||||
const heightToUse = _height - (MARGIN * 2);
|
||||
const widthToUse = _width - (MARGIN * 2);
|
||||
let tileWidth;
|
||||
|
||||
// If there is going to be at least two rows, ensure that at least two
|
||||
// rows display fully on screen.
|
||||
if (participantCount / columns > 1) {
|
||||
tileWidth
|
||||
= Math.min(widthToUse / columns, heightToUse / 2);
|
||||
tileWidth = Math.min(widthToUse / columns, heightToUse / 2);
|
||||
} else {
|
||||
tileWidth = Math.min(widthToUse / columns, heightToUse);
|
||||
}
|
||||
@@ -247,8 +217,7 @@ class TileView extends Component<Props, State> {
|
||||
|
||||
for (let i = 0; i < thumbnails.length; i++) {
|
||||
if (i % rowLength === 0) {
|
||||
const thumbnailsInRow
|
||||
= thumbnails.slice(i, i + rowLength);
|
||||
const thumbnailsInRow = thumbnails.slice(i, i + rowLength);
|
||||
|
||||
rowElements.push(
|
||||
<View
|
||||
@@ -263,23 +232,6 @@ class TileView extends Component<Props, State> {
|
||||
return rowElements;
|
||||
}
|
||||
|
||||
_onDimensionsChanged: (width: number, height: number) => void;
|
||||
|
||||
/**
|
||||
* Updates the known available state for {@link TileView} to occupy.
|
||||
*
|
||||
* @param {number} width - The component's current width.
|
||||
* @param {number} height - The component's current height.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDimensionsChanged(width: number, height: number) {
|
||||
this.setState({
|
||||
height,
|
||||
width
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates React Elements to display each participant in a thumbnail. Each
|
||||
* tile will be.
|
||||
@@ -326,14 +278,17 @@ class TileView extends Component<Props, State> {
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _participants: Participant[]
|
||||
* }}
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const responsiveUi = state['features/base/responsive-ui'];
|
||||
|
||||
return {
|
||||
_participants: state['features/base/participants']
|
||||
_aspectRatio: responsiveUi.aspectRatio,
|
||||
_height: responsiveUi.clientHeight,
|
||||
_participants: state['features/base/participants'],
|
||||
_width: responsiveUi.clientWidth
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(makeAspectRatioAware(TileView));
|
||||
export default connect(_mapStateToProps)(TileView);
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../base/color-scheme';
|
||||
import { ParticipantView } from '../../base/participants';
|
||||
import { connect } from '../../base/redux';
|
||||
import { DimensionsDetector } from '../../base/responsive-ui';
|
||||
import { StyleType } from '../../base/styles';
|
||||
|
||||
import { AVATAR_SIZE } from './styles';
|
||||
@@ -15,6 +14,11 @@ import { AVATAR_SIZE } from './styles';
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Application's viewport height.
|
||||
*/
|
||||
_height: number,
|
||||
|
||||
/**
|
||||
* The ID of the participant (to be) depicted by LargeVideo.
|
||||
*
|
||||
@@ -27,6 +31,11 @@ type Props = {
|
||||
*/
|
||||
_styles: StyleType,
|
||||
|
||||
/**
|
||||
* Application's viewport height.
|
||||
*/
|
||||
_width: number,
|
||||
|
||||
/**
|
||||
* Callback to invoke when the {@code LargeVideo} is clicked/pressed.
|
||||
*/
|
||||
@@ -62,50 +71,33 @@ const DEFAULT_STATE = {
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class LargeVideo extends Component<Props, State> {
|
||||
class LargeVideo extends PureComponent<Props, State> {
|
||||
state = {
|
||||
...DEFAULT_STATE
|
||||
};
|
||||
|
||||
/** Initializes a new {@code LargeVideo} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onDimensionsChanged = this._onDimensionsChanged.bind(this);
|
||||
}
|
||||
|
||||
_onDimensionsChanged: (width: number, height: number) => void;
|
||||
|
||||
/**
|
||||
* Handle this component's dimension changes. In case we deem it's too
|
||||
* Handles dimension changes. In case we deem it's too
|
||||
* small, the connectivity indicator won't be rendered and the avatar
|
||||
* will occupy the entirety of the available screen state.
|
||||
*
|
||||
* @param {number} width - The component's current width.
|
||||
* @param {number} height - The component's current height.
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @inheritdoc
|
||||
*/
|
||||
_onDimensionsChanged(width: number, height: number) {
|
||||
static getDerivedStateFromProps(props: Props) {
|
||||
const { _height, _width } = props;
|
||||
|
||||
// Get the size, rounded to the nearest even number.
|
||||
const size = 2 * Math.round(Math.min(height, width) / 2);
|
||||
let nextState;
|
||||
const size = 2 * Math.round(Math.min(_height, _width) / 2);
|
||||
|
||||
if (size < AVATAR_SIZE * 1.5) {
|
||||
nextState = {
|
||||
return {
|
||||
avatarSize: size - 15, // Leave some margin.
|
||||
useConnectivityInfoLabel: false
|
||||
};
|
||||
} else {
|
||||
nextState = DEFAULT_STATE;
|
||||
}
|
||||
|
||||
this.setState(nextState);
|
||||
return DEFAULT_STATE;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,18 +118,15 @@ class LargeVideo extends Component<Props, State> {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<DimensionsDetector
|
||||
onDimensionsChanged = { this._onDimensionsChanged }>
|
||||
<ParticipantView
|
||||
avatarSize = { avatarSize }
|
||||
onPress = { onClick }
|
||||
participantId = { _participantId }
|
||||
style = { _styles.largeVideo }
|
||||
testHintId = 'org.jitsi.meet.LargeVideo'
|
||||
useConnectivityInfoLabel = { useConnectivityInfoLabel }
|
||||
zOrder = { 0 }
|
||||
zoomEnabled = { true } />
|
||||
</DimensionsDetector>
|
||||
<ParticipantView
|
||||
avatarSize = { avatarSize }
|
||||
onPress = { onClick }
|
||||
participantId = { _participantId }
|
||||
style = { _styles.largeVideo }
|
||||
testHintId = 'org.jitsi.meet.LargeVideo'
|
||||
useConnectivityInfoLabel = { useConnectivityInfoLabel }
|
||||
zOrder = { 0 }
|
||||
zoomEnabled = { true } />
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -147,15 +136,16 @@ class LargeVideo extends Component<Props, State> {
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _participantId: string,
|
||||
* _styles: StyleType
|
||||
* }}
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { clientHeight: height, clientWidth: width } = state['features/base/responsive-ui'];
|
||||
|
||||
return {
|
||||
_height: height,
|
||||
_participantId: state['features/large-video'].participantId,
|
||||
_styles: ColorSchemeRegistry.get(state, 'LargeVideo')
|
||||
_styles: ColorSchemeRegistry.get(state, 'LargeVideo'),
|
||||
_width: width
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,11 @@ export type Props = {
|
||||
*/
|
||||
isDismissAllowed: boolean,
|
||||
|
||||
/**
|
||||
* Maximum lines of the description.
|
||||
*/
|
||||
maxLines: ?number,
|
||||
|
||||
/**
|
||||
* Callback invoked when the user clicks to dismiss the notification.
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,13 @@ import AbstractNotification, {
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* Default value for the maxLines prop.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
const DEFAULT_MAX_LINES = 1;
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} to display a notification.
|
||||
*
|
||||
@@ -24,9 +31,7 @@ class Notification extends AbstractNotification<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
isDismissAllowed
|
||||
} = this.props;
|
||||
const { isDismissAllowed } = this.props;
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -61,7 +66,7 @@ class Notification extends AbstractNotification<Props> {
|
||||
* @private
|
||||
*/
|
||||
_renderContent() {
|
||||
const { t, title, titleArguments, titleKey } = this.props;
|
||||
const { maxLines = DEFAULT_MAX_LINES, t, title, titleArguments, titleKey } = this.props;
|
||||
const titleText = title || (titleKey && t(titleKey, titleArguments));
|
||||
const description = this._getDescription();
|
||||
|
||||
@@ -69,7 +74,7 @@ class Notification extends AbstractNotification<Props> {
|
||||
return description.map((line, index) => (
|
||||
<Text
|
||||
key = { index }
|
||||
numberOfLines = { 1 }
|
||||
numberOfLines = { maxLines }
|
||||
style = { styles.contentText }>
|
||||
{ line }
|
||||
</Text>
|
||||
@@ -78,7 +83,7 @@ class Notification extends AbstractNotification<Props> {
|
||||
|
||||
return (
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
numberOfLines = { maxLines }
|
||||
style = { styles.contentText } >
|
||||
{ titleText }
|
||||
</Text>
|
||||
|
||||
@@ -40,7 +40,7 @@ export default {
|
||||
notification: {
|
||||
backgroundColor: '#768898',
|
||||
flexDirection: 'row',
|
||||
height: 48,
|
||||
minHeight: 48,
|
||||
marginTop: 0.5 * BoxModel.margin
|
||||
},
|
||||
|
||||
|
||||
42
react/features/recording/actions.native.js
Normal file
42
react/features/recording/actions.native.js
Normal file
@@ -0,0 +1,42 @@
|
||||
// @flow
|
||||
|
||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||
import { showNotification } from '../notifications';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Signals that a started recording notification should be shown on the
|
||||
* screen for a given period.
|
||||
*
|
||||
* @param {string} streamType - The type of the stream ({@code file} or
|
||||
* {@code stream}).
|
||||
* @returns {showNotification}
|
||||
*/
|
||||
export function showRecordingLimitNotification(streamType: string) {
|
||||
return (dispatch: Function, getState: Function) => {
|
||||
const isLiveStreaming = streamType === JitsiMeetJS.constants.recording.mode.STREAM;
|
||||
let descriptionKey, titleKey;
|
||||
|
||||
if (isLiveStreaming) {
|
||||
descriptionKey = 'liveStreaming.limitNotificationDescriptionNative';
|
||||
titleKey = 'dialog.liveStreaming';
|
||||
} else {
|
||||
descriptionKey = 'recording.limitNotificationDescriptionNative';
|
||||
titleKey = 'dialog.recording';
|
||||
}
|
||||
|
||||
const { recordingLimit = {} } = getState()['features/base/config'];
|
||||
const { limit, appName } = recordingLimit;
|
||||
|
||||
return dispatch(showNotification({
|
||||
descriptionArguments: {
|
||||
limit,
|
||||
app: appName
|
||||
},
|
||||
descriptionKey,
|
||||
titleKey,
|
||||
maxLines: 2
|
||||
}, 10000));
|
||||
};
|
||||
}
|
||||
27
react/features/recording/actions.web.js
Normal file
27
react/features/recording/actions.web.js
Normal file
@@ -0,0 +1,27 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||
import { showNotification } from '../notifications';
|
||||
|
||||
import { RecordingLimitNotificationDescription } from './components';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Signals that a started recording notification should be shown on the
|
||||
* screen for a given period.
|
||||
*
|
||||
* @param {string} streamType - The type of the stream ({@code file} or
|
||||
* {@code stream}).
|
||||
* @returns {showNotification}
|
||||
*/
|
||||
export function showRecordingLimitNotification(streamType: string) {
|
||||
const isLiveStreaming = streamType === JitsiMeetJS.constants.recording.mode.STREAM;
|
||||
|
||||
return showNotification({
|
||||
description: <RecordingLimitNotificationDescription isLiveStreaming = { isLiveStreaming } />,
|
||||
titleKey: isLiveStreaming ? 'dialog.liveStreaming' : 'dialog.recording'
|
||||
}, 10000);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { translate, translateToHTML } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link RecordingLimitNotificationDescription}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The limit of time in minutes for the recording.
|
||||
*/
|
||||
_limit: number,
|
||||
|
||||
/**
|
||||
* The name of the app with unlimited recordings.
|
||||
*/
|
||||
_appName: string,
|
||||
|
||||
/**
|
||||
* The URL to the app with unlimited recordings.
|
||||
*/
|
||||
_appURL: string,
|
||||
|
||||
/**
|
||||
* True if the notification is related to the livestreaming and false if not.
|
||||
*/
|
||||
isLiveStreaming: Boolean,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* A component that renders the description of the notification for the recording initiator.
|
||||
*
|
||||
* @param {Props} props - The props of the component.
|
||||
* @returns {Component}
|
||||
*/
|
||||
function RecordingLimitNotificationDescription(props: Props) {
|
||||
const { _limit, _appName, _appURL, isLiveStreaming, t } = props;
|
||||
|
||||
return (
|
||||
<span>
|
||||
{
|
||||
translateToHTML(
|
||||
t,
|
||||
`${isLiveStreaming ? 'liveStreaming' : 'recording'}.limitNotificationDescriptionWeb`, {
|
||||
limit: _limit,
|
||||
app: _appName,
|
||||
url: _appURL
|
||||
})
|
||||
}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maps part of the Redix state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state): $Shape<Props> {
|
||||
const { recordingLimit = {} } = state['features/base/config'];
|
||||
const { limit: _limit, appName: _appName, appURL: _appURL } = recordingLimit;
|
||||
|
||||
return {
|
||||
_limit,
|
||||
_appName,
|
||||
_appURL
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(RecordingLimitNotificationDescription));
|
||||
@@ -1,3 +1,4 @@
|
||||
// @flow
|
||||
|
||||
export { default as RecordingLabel } from './RecordingLabel';
|
||||
export { default as RecordingLimitNotificationDescription } from './RecordingLimitNotificationDescription';
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
hidePendingRecordingNotification,
|
||||
showPendingRecordingNotification,
|
||||
showRecordingError,
|
||||
showRecordingLimitNotification,
|
||||
showStartedRecordingNotification,
|
||||
showStoppedRecordingNotification,
|
||||
updateRecordingSessionData
|
||||
@@ -44,6 +45,8 @@ import {
|
||||
RECORDING_ON_SOUND_FILE
|
||||
} from './sounds';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* StateListenerRegistry provides a reliable way to detect the leaving of a
|
||||
* conference, where we need to clean up the recording sessions.
|
||||
@@ -131,7 +134,8 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
const {
|
||||
iAmRecorder,
|
||||
iAmSipGateway,
|
||||
disableRecordAudioNotification
|
||||
disableRecordAudioNotification,
|
||||
recordingLimit
|
||||
} = getState()['features/base/config'];
|
||||
|
||||
if (iAmRecorder && !iAmSipGateway) {
|
||||
@@ -151,9 +155,16 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
|
||||
if (updatedSessionData.status === ON
|
||||
&& (!oldSessionData || oldSessionData.status !== ON)) {
|
||||
const initiatorName = initiator && getParticipantDisplayName(getState, initiator.getId());
|
||||
if (initiator) {
|
||||
const initiatorName = initiator && getParticipantDisplayName(getState, initiator.getId());
|
||||
|
||||
initiatorName && dispatch(showStartedRecordingNotification(mode, initiatorName));
|
||||
} else if (typeof recordingLimit === 'object') {
|
||||
// Show notification with additional information to the initiator.
|
||||
dispatch(showRecordingLimitNotification(mode));
|
||||
}
|
||||
|
||||
|
||||
initiatorName && dispatch(showStartedRecordingNotification(mode, initiatorName));
|
||||
sendAnalytics(createRecordingEvent('start', mode));
|
||||
|
||||
if (disableRecordAudioNotification) {
|
||||
|
||||
Reference in New Issue
Block a user