Make web use the redux settings/profile
This commit is contained in:
parent
ab7e572162
commit
959db3a665
2
app.js
2
app.js
|
@ -15,7 +15,6 @@ import conference from './conference';
|
||||||
import API from './modules/API';
|
import API from './modules/API';
|
||||||
import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';
|
import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';
|
||||||
import remoteControl from './modules/remotecontrol/RemoteControl';
|
import remoteControl from './modules/remotecontrol/RemoteControl';
|
||||||
import settings from './modules/settings/Settings';
|
|
||||||
import translation from './modules/translation/translation';
|
import translation from './modules/translation/translation';
|
||||||
import UI from './modules/UI/UI';
|
import UI from './modules/UI/UI';
|
||||||
|
|
||||||
|
@ -41,7 +40,6 @@ window.APP = {
|
||||||
|
|
||||||
keyboardshortcut,
|
keyboardshortcut,
|
||||||
remoteControl,
|
remoteControl,
|
||||||
settings,
|
|
||||||
translation,
|
translation,
|
||||||
UI
|
UI
|
||||||
};
|
};
|
||||||
|
|
|
@ -46,7 +46,10 @@ import {
|
||||||
sendLocalParticipant,
|
sendLocalParticipant,
|
||||||
setDesktopSharingEnabled
|
setDesktopSharingEnabled
|
||||||
} from './react/features/base/conference';
|
} from './react/features/base/conference';
|
||||||
import { updateDeviceList } from './react/features/base/devices';
|
import {
|
||||||
|
setAudioOutputDeviceId,
|
||||||
|
updateDeviceList
|
||||||
|
} from './react/features/base/devices';
|
||||||
import {
|
import {
|
||||||
isFatalJitsiConnectionError,
|
isFatalJitsiConnectionError,
|
||||||
JitsiConferenceErrors,
|
JitsiConferenceErrors,
|
||||||
|
@ -82,6 +85,7 @@ import {
|
||||||
participantRoleChanged,
|
participantRoleChanged,
|
||||||
participantUpdated
|
participantUpdated
|
||||||
} from './react/features/base/participants';
|
} from './react/features/base/participants';
|
||||||
|
import { updateSettings } from './react/features/base/settings';
|
||||||
import {
|
import {
|
||||||
createLocalTracksF,
|
createLocalTracksF,
|
||||||
isLocalTrackMuted,
|
isLocalTrackMuted,
|
||||||
|
@ -1273,7 +1277,7 @@ export default {
|
||||||
: 'colibri';
|
: 'colibri';
|
||||||
}
|
}
|
||||||
|
|
||||||
const nick = APP.settings.getDisplayName();
|
const nick = APP.store.getState()['features/base/settings'].displayName;
|
||||||
|
|
||||||
if (nick) {
|
if (nick) {
|
||||||
options.displayName = nick;
|
options.displayName = nick;
|
||||||
|
@ -2131,7 +2135,9 @@ export default {
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.log('switched local video device');
|
logger.log('switched local video device');
|
||||||
APP.settings.setCameraDeviceId(cameraDeviceId, true);
|
APP.store.dispatch(updateSettings({
|
||||||
|
cameraDeviceId
|
||||||
|
}));
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
APP.UI.showCameraErrorNotification(err);
|
APP.UI.showCameraErrorNotification(err);
|
||||||
|
@ -2163,7 +2169,9 @@ export default {
|
||||||
.then(stream => {
|
.then(stream => {
|
||||||
this.useAudioStream(stream);
|
this.useAudioStream(stream);
|
||||||
logger.log('switched local audio device');
|
logger.log('switched local audio device');
|
||||||
APP.settings.setMicDeviceId(micDeviceId, true);
|
APP.store.dispatch(updateSettings({
|
||||||
|
micDeviceId
|
||||||
|
}));
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
APP.UI.showMicErrorNotification(err);
|
APP.UI.showMicErrorNotification(err);
|
||||||
|
@ -2175,7 +2183,7 @@ export default {
|
||||||
UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
|
UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
|
||||||
audioOutputDeviceId => {
|
audioOutputDeviceId => {
|
||||||
sendAnalytics(createDeviceChangedEvent('audio', 'output'));
|
sendAnalytics(createDeviceChangedEvent('audio', 'output'));
|
||||||
APP.settings.setAudioOutputDeviceId(audioOutputDeviceId)
|
setAudioOutputDeviceId(audioOutputDeviceId)
|
||||||
.then(() => logger.log('changed audio output device'))
|
.then(() => logger.log('changed audio output device'))
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
logger.warn('Failed to change audio output device. '
|
logger.warn('Failed to change audio output device. '
|
||||||
|
@ -2317,7 +2325,8 @@ export default {
|
||||||
APP.store.dispatch(conferenceJoined(room));
|
APP.store.dispatch(conferenceJoined(room));
|
||||||
|
|
||||||
APP.UI.mucJoined();
|
APP.UI.mucJoined();
|
||||||
const displayName = APP.settings.getDisplayName();
|
const displayName
|
||||||
|
= APP.store.getState()['features/base/settings'].displayName;
|
||||||
|
|
||||||
APP.API.notifyConferenceJoined(
|
APP.API.notifyConferenceJoined(
|
||||||
this.roomName,
|
this.roomName,
|
||||||
|
@ -2367,14 +2376,18 @@ export default {
|
||||||
// storage and settings menu. This is a workaround until
|
// storage and settings menu. This is a workaround until
|
||||||
// getConstraints() method will be implemented
|
// getConstraints() method will be implemented
|
||||||
// in browsers.
|
// in browsers.
|
||||||
|
const { dispatch } = APP.store;
|
||||||
|
|
||||||
if (this.localAudio) {
|
if (this.localAudio) {
|
||||||
APP.settings.setMicDeviceId(
|
dispatch(updateSettings({
|
||||||
this.localAudio.getDeviceId(), false);
|
micDeviceId: this.localAudio.getDeviceId()
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.localVideo) {
|
if (this.localVideo) {
|
||||||
APP.settings.setCameraDeviceId(
|
dispatch(updateSettings({
|
||||||
this.localVideo.getDeviceId(), false);
|
cameraDeviceId: this.localVideo.getDeviceId()
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaDeviceHelper.setCurrentMediaDevices(devices);
|
mediaDeviceHelper.setCurrentMediaDevices(devices);
|
||||||
|
@ -2426,8 +2439,7 @@ export default {
|
||||||
|
|
||||||
if (typeof newDevices.audiooutput !== 'undefined') {
|
if (typeof newDevices.audiooutput !== 'undefined') {
|
||||||
// Just ignore any errors in catch block.
|
// Just ignore any errors in catch block.
|
||||||
promises.push(APP.settings
|
promises.push(setAudioOutputDeviceId(newDevices.audiooutput)
|
||||||
.setAudioOutputDeviceId(newDevices.audiooutput)
|
|
||||||
.catch());
|
.catch());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2576,7 +2588,10 @@ export default {
|
||||||
email: formattedEmail
|
email: formattedEmail
|
||||||
}));
|
}));
|
||||||
|
|
||||||
APP.settings.setEmail(formattedEmail);
|
APP.store.dispatch(updateSettings({
|
||||||
|
email: formattedEmail
|
||||||
|
}));
|
||||||
|
|
||||||
APP.UI.setUserEmail(localId, formattedEmail);
|
APP.UI.setUserEmail(localId, formattedEmail);
|
||||||
sendData(commands.EMAIL, formattedEmail);
|
sendData(commands.EMAIL, formattedEmail);
|
||||||
},
|
},
|
||||||
|
@ -2600,7 +2615,9 @@ export default {
|
||||||
avatarURL: formattedUrl
|
avatarURL: formattedUrl
|
||||||
}));
|
}));
|
||||||
|
|
||||||
APP.settings.setAvatarUrl(url);
|
APP.store.dispatch(updateSettings({
|
||||||
|
avatarURL: formattedUrl
|
||||||
|
}));
|
||||||
sendData(commands.AVATAR_URL, url);
|
sendData(commands.AVATAR_URL, url);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2654,7 +2671,10 @@ export default {
|
||||||
name: formattedNickname
|
name: formattedNickname
|
||||||
}));
|
}));
|
||||||
|
|
||||||
APP.settings.setDisplayName(formattedNickname);
|
APP.store.dispatch(updateSettings({
|
||||||
|
displayName: formattedNickname
|
||||||
|
}));
|
||||||
|
|
||||||
APP.API.notifyDisplayNameChanged(id, {
|
APP.API.notifyDisplayNameChanged(id, {
|
||||||
displayName: formattedNickname,
|
displayName: formattedNickname,
|
||||||
formattedDisplayName:
|
formattedDisplayName:
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* global $, APP */
|
/* global $, APP */
|
||||||
import UIUtil from '../../util/UIUtil';
|
import UIUtil from '../../util/UIUtil';
|
||||||
import UIEvents from '../../../../service/UI/UIEvents';
|
import UIEvents from '../../../../service/UI/UIEvents';
|
||||||
import Settings from '../../../settings/Settings';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createProfilePanelButtonEvent,
|
createProfilePanelButtonEvent,
|
||||||
|
@ -54,6 +53,8 @@ export default {
|
||||||
init(emitter) {
|
init(emitter) {
|
||||||
initHTML();
|
initHTML();
|
||||||
|
|
||||||
|
const settings = APP.store.getState()['features/base/settings'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates display name.
|
* Updates display name.
|
||||||
*
|
*
|
||||||
|
@ -64,7 +65,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#setDisplayName')
|
$('#setDisplayName')
|
||||||
.val(Settings.getDisplayName())
|
.val(settings.displayName)
|
||||||
.keyup(event => {
|
.keyup(event => {
|
||||||
if (event.keyCode === 13) { // enter
|
if (event.keyCode === 13) { // enter
|
||||||
updateDisplayName();
|
updateDisplayName();
|
||||||
|
@ -82,7 +83,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#setEmail')
|
$('#setEmail')
|
||||||
.val(Settings.getEmail())
|
.val(settings.email)
|
||||||
.keyup(event => {
|
.keyup(event => {
|
||||||
if (event.keyCode === 13) { // enter
|
if (event.keyCode === 13) { // enter
|
||||||
updateEmail();
|
updateEmail();
|
||||||
|
|
|
@ -74,17 +74,6 @@ const UIUtil = {
|
||||||
.html();
|
.html();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Unescapes the given text.
|
|
||||||
*
|
|
||||||
* @param {string} safe string which contains escaped html
|
|
||||||
* @returns {string} unescaped html string.
|
|
||||||
*/
|
|
||||||
unescapeHtml(safe) {
|
|
||||||
return $('<div />').html(safe)
|
|
||||||
.text();
|
|
||||||
},
|
|
||||||
|
|
||||||
imageToGrayScale(canvas) {
|
imageToGrayScale(canvas) {
|
||||||
const context = canvas.getContext('2d');
|
const context = canvas.getContext('2d');
|
||||||
const imgData = context.getImageData(0, 0, canvas.width, canvas.height);
|
const imgData = context.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { VideoTrack } from '../../../react/features/base/media';
|
||||||
import {
|
import {
|
||||||
getAvatarURLByParticipantId
|
getAvatarURLByParticipantId
|
||||||
} from '../../../react/features/base/participants';
|
} from '../../../react/features/base/participants';
|
||||||
|
import { updateSettings } from '../../../react/features/base/settings';
|
||||||
/* eslint-enable no-unused-vars */
|
/* eslint-enable no-unused-vars */
|
||||||
|
|
||||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||||
|
@ -121,9 +122,10 @@ LocalVideo.prototype.changeVideo = function(stream) {
|
||||||
|
|
||||||
// eslint-disable-next-line eqeqeq
|
// eslint-disable-next-line eqeqeq
|
||||||
const isVideo = stream.videoType != 'desktop';
|
const isVideo = stream.videoType != 'desktop';
|
||||||
|
const settings = APP.store.getState()['features/base/settings'];
|
||||||
|
|
||||||
this._enableDisableContextMenu(isVideo);
|
this._enableDisableContextMenu(isVideo);
|
||||||
this.setFlipX(isVideo ? APP.settings.getLocalFlipX() : false);
|
this.setFlipX(isVideo ? settings.localFlipX : false);
|
||||||
|
|
||||||
const endedHandler = () => {
|
const endedHandler = () => {
|
||||||
|
|
||||||
|
@ -194,10 +196,14 @@ LocalVideo.prototype._buildContextMenu = function() {
|
||||||
flip: {
|
flip: {
|
||||||
name: 'Flip',
|
name: 'Flip',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const val = !APP.settings.getLocalFlipX();
|
const { store } = APP;
|
||||||
|
const val = !store.getState()['features/base/settings']
|
||||||
|
.localFlipX;
|
||||||
|
|
||||||
this.setFlipX(val);
|
this.setFlipX(val);
|
||||||
APP.settings.setLocalFlipX(val);
|
store.dispatch(updateSettings({
|
||||||
|
localFlipX: val
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
/* global APP, JitsiMeetJS */
|
/* global APP, JitsiMeetJS */
|
||||||
|
|
||||||
|
import { getAudioOutputDeviceId } from '../../react/features/base/devices';
|
||||||
|
|
||||||
let currentAudioInputDevices,
|
let currentAudioInputDevices,
|
||||||
currentAudioOutputDevices,
|
currentAudioOutputDevices,
|
||||||
currentVideoInputDevices;
|
currentVideoInputDevices;
|
||||||
|
@ -16,7 +18,7 @@ function getNewAudioOutputDevice(newDevices) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedAudioOutputDeviceId = APP.settings.getAudioOutputDeviceId();
|
const selectedAudioOutputDeviceId = getAudioOutputDeviceId();
|
||||||
const availableAudioOutputDevices = newDevices.filter(
|
const availableAudioOutputDevices = newDevices.filter(
|
||||||
d => d.kind === 'audiooutput');
|
d => d.kind === 'audiooutput');
|
||||||
|
|
||||||
|
@ -40,7 +42,8 @@ function getNewAudioOutputDevice(newDevices) {
|
||||||
function getNewAudioInputDevice(newDevices, localAudio) {
|
function getNewAudioInputDevice(newDevices, localAudio) {
|
||||||
const availableAudioInputDevices = newDevices.filter(
|
const availableAudioInputDevices = newDevices.filter(
|
||||||
d => d.kind === 'audioinput');
|
d => d.kind === 'audioinput');
|
||||||
const selectedAudioInputDeviceId = APP.settings.getMicDeviceId();
|
const settings = APP.store.getState()['features/base/settings'];
|
||||||
|
const selectedAudioInputDeviceId = settings.micDeviceId;
|
||||||
const selectedAudioInputDevice = availableAudioInputDevices.find(
|
const selectedAudioInputDevice = availableAudioInputDevices.find(
|
||||||
d => d.deviceId === selectedAudioInputDeviceId);
|
d => d.deviceId === selectedAudioInputDeviceId);
|
||||||
|
|
||||||
|
@ -76,7 +79,8 @@ function getNewAudioInputDevice(newDevices, localAudio) {
|
||||||
function getNewVideoInputDevice(newDevices, localVideo) {
|
function getNewVideoInputDevice(newDevices, localVideo) {
|
||||||
const availableVideoInputDevices = newDevices.filter(
|
const availableVideoInputDevices = newDevices.filter(
|
||||||
d => d.kind === 'videoinput');
|
d => d.kind === 'videoinput');
|
||||||
const selectedVideoInputDeviceId = APP.settings.getCameraDeviceId();
|
const settings = APP.store.getState()['features/base/settings'];
|
||||||
|
const selectedVideoInputDeviceId = settings.cameraDeviceId;
|
||||||
const selectedVideoInputDevice = availableVideoInputDevices.find(
|
const selectedVideoInputDevice = availableVideoInputDevices.find(
|
||||||
d => d.deviceId === selectedVideoInputDeviceId);
|
d => d.deviceId === selectedVideoInputDeviceId);
|
||||||
|
|
||||||
|
|
|
@ -1,191 +0,0 @@
|
||||||
/* global JitsiMeetJS */
|
|
||||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
|
||||||
|
|
||||||
import UIUtil from '../UI/util/UIUtil';
|
|
||||||
import jitsiLocalStorage from '../util/JitsiLocalStorage';
|
|
||||||
import { randomHexString } from '../../react/features/base/util';
|
|
||||||
|
|
||||||
let avatarUrl = '';
|
|
||||||
|
|
||||||
let email = UIUtil.unescapeHtml(jitsiLocalStorage.getItem('email') || '');
|
|
||||||
let avatarId = UIUtil.unescapeHtml(jitsiLocalStorage.getItem('avatarId') || '');
|
|
||||||
|
|
||||||
if (!avatarId) {
|
|
||||||
// if there is no avatar id, we generate a unique one and use it forever
|
|
||||||
avatarId = randomHexString(32);
|
|
||||||
jitsiLocalStorage.setItem('avatarId', avatarId);
|
|
||||||
}
|
|
||||||
|
|
||||||
let localFlipX = JSON.parse(jitsiLocalStorage.getItem('localFlipX') || true);
|
|
||||||
let displayName = UIUtil.unescapeHtml(
|
|
||||||
jitsiLocalStorage.getItem('displayname') || '');
|
|
||||||
let cameraDeviceId = jitsiLocalStorage.getItem('cameraDeviceId') || '';
|
|
||||||
let micDeviceId = jitsiLocalStorage.getItem('micDeviceId') || '';
|
|
||||||
|
|
||||||
// Currently audio output device change is supported only in Chrome and
|
|
||||||
// default output always has 'default' device ID
|
|
||||||
const audioOutputDeviceId = jitsiLocalStorage.getItem('audioOutputDeviceId')
|
|
||||||
|| 'default';
|
|
||||||
|
|
||||||
if (audioOutputDeviceId
|
|
||||||
!== JitsiMeetJS.mediaDevices.getAudioOutputDevice()) {
|
|
||||||
JitsiMeetJS.mediaDevices.setAudioOutputDevice(audioOutputDeviceId)
|
|
||||||
.catch(ex => {
|
|
||||||
logger.warn('Failed to set audio output device from local '
|
|
||||||
+ 'storage. Default audio output device will be used'
|
|
||||||
+ 'instead.', ex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the local user display name and saves it to local storage
|
|
||||||
*
|
|
||||||
* @param {string} newDisplayName unescaped display name for the local user
|
|
||||||
* @param {boolean} disableLocalStore disables local store the display name
|
|
||||||
*/
|
|
||||||
setDisplayName(newDisplayName, disableLocalStore) {
|
|
||||||
displayName = newDisplayName;
|
|
||||||
|
|
||||||
if (!disableLocalStore) {
|
|
||||||
jitsiLocalStorage.setItem('displayname',
|
|
||||||
UIUtil.escapeHtml(displayName));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the escaped display name currently used by the user
|
|
||||||
* @returns {string} currently valid user display name.
|
|
||||||
*/
|
|
||||||
getDisplayName() {
|
|
||||||
return displayName;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets new email for local user and saves it to the local storage.
|
|
||||||
* @param {string} newEmail new email for the local user
|
|
||||||
* @param {boolean} disableLocalStore disables local store the email
|
|
||||||
*/
|
|
||||||
setEmail(newEmail, disableLocalStore) {
|
|
||||||
email = newEmail;
|
|
||||||
|
|
||||||
if (!disableLocalStore) {
|
|
||||||
jitsiLocalStorage.setItem('email', UIUtil.escapeHtml(newEmail));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns email address of the local user.
|
|
||||||
* @returns {string} email
|
|
||||||
*/
|
|
||||||
getEmail() {
|
|
||||||
return email;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns avatar id of the local user.
|
|
||||||
* @returns {string} avatar id
|
|
||||||
*/
|
|
||||||
getAvatarId() {
|
|
||||||
return avatarId;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets new avatarUrl for local user and saves it to the local storage.
|
|
||||||
* @param {string} newAvatarUrl new avatarUrl for the local user
|
|
||||||
*/
|
|
||||||
setAvatarUrl(newAvatarUrl) {
|
|
||||||
avatarUrl = newAvatarUrl;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns avatarUrl address of the local user.
|
|
||||||
* @returns {string} avatarUrl
|
|
||||||
*/
|
|
||||||
getAvatarUrl() {
|
|
||||||
return avatarUrl;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets new flipX state of local video and saves it to the local storage.
|
|
||||||
* @param {string} val flipX state of local video
|
|
||||||
*/
|
|
||||||
setLocalFlipX(val) {
|
|
||||||
localFlipX = val;
|
|
||||||
jitsiLocalStorage.setItem('localFlipX', val);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns flipX state of local video.
|
|
||||||
* @returns {string} flipX
|
|
||||||
*/
|
|
||||||
getLocalFlipX() {
|
|
||||||
return localFlipX;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get device id of the camera which is currently in use.
|
|
||||||
* Empty string stands for default device.
|
|
||||||
* @returns {String}
|
|
||||||
*/
|
|
||||||
getCameraDeviceId() {
|
|
||||||
return cameraDeviceId;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set device id of the camera which is currently in use.
|
|
||||||
* Empty string stands for default device.
|
|
||||||
* @param {string} newId new camera device id
|
|
||||||
* @param {boolean} whether we need to store the value
|
|
||||||
*/
|
|
||||||
setCameraDeviceId(newId, store) {
|
|
||||||
cameraDeviceId = newId;
|
|
||||||
if (store) {
|
|
||||||
jitsiLocalStorage.setItem('cameraDeviceId', newId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get device id of the microphone which is currently in use.
|
|
||||||
* Empty string stands for default device.
|
|
||||||
* @returns {String}
|
|
||||||
*/
|
|
||||||
getMicDeviceId() {
|
|
||||||
return micDeviceId;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set device id of the microphone which is currently in use.
|
|
||||||
* Empty string stands for default device.
|
|
||||||
* @param {string} newId new microphone device id
|
|
||||||
* @param {boolean} whether we need to store the value
|
|
||||||
*/
|
|
||||||
setMicDeviceId(newId, store) {
|
|
||||||
micDeviceId = newId;
|
|
||||||
if (store) {
|
|
||||||
jitsiLocalStorage.setItem('micDeviceId', newId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get device id of the audio output device which is currently in use.
|
|
||||||
* Empty string stands for default device.
|
|
||||||
* @returns {String}
|
|
||||||
*/
|
|
||||||
getAudioOutputDeviceId() {
|
|
||||||
return JitsiMeetJS.mediaDevices.getAudioOutputDevice();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set device id of the audio output device which is currently in use.
|
|
||||||
* Empty string stands for default device.
|
|
||||||
* @param {string} newId='default' - new audio output device id
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
setAudioOutputDeviceId(newId = 'default') {
|
|
||||||
return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
|
|
||||||
.then(() =>
|
|
||||||
jitsiLocalStorage.setItem('audioOutputDeviceId', newId));
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -12,7 +12,6 @@ import {
|
||||||
localParticipantJoined,
|
localParticipantJoined,
|
||||||
localParticipantLeft
|
localParticipantLeft
|
||||||
} from '../../base/participants';
|
} from '../../base/participants';
|
||||||
import '../../base/profile';
|
|
||||||
import { Fragment, RouteRegistry } from '../../base/react';
|
import { Fragment, RouteRegistry } from '../../base/react';
|
||||||
import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
|
import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
|
||||||
import { SoundCollection } from '../../base/sounds';
|
import { SoundCollection } from '../../base/sounds';
|
||||||
|
@ -26,6 +25,9 @@ import { appNavigate, appWillMount, appWillUnmount } from '../actions';
|
||||||
/**
|
/**
|
||||||
* The default URL to open if no other was specified to {@code AbstractApp}
|
* The default URL to open if no other was specified to {@code AbstractApp}
|
||||||
* via props.
|
* via props.
|
||||||
|
*
|
||||||
|
* FIXME: This is not at the best place here. This should be either in the
|
||||||
|
* base/settings feature or a default in base/config.
|
||||||
*/
|
*/
|
||||||
const DEFAULT_URL = 'https://meet.jit.si';
|
const DEFAULT_URL = 'https://meet.jit.si';
|
||||||
|
|
||||||
|
@ -133,26 +135,14 @@ export class AbstractApp extends Component {
|
||||||
// actions is important, not the call site. Moreover, we've got
|
// actions is important, not the call site. Moreover, we've got
|
||||||
// localParticipant business logic in the React Component
|
// localParticipant business logic in the React Component
|
||||||
// (i.e. UI) AbstractApp now.
|
// (i.e. UI) AbstractApp now.
|
||||||
let localParticipant = {};
|
|
||||||
|
|
||||||
if (typeof APP === 'object') {
|
const settings = getState()['features/base/settings'];
|
||||||
localParticipant = {
|
const localParticipant = {
|
||||||
avatarID: APP.settings.getAvatarId(),
|
avatarID: settings.avatarID,
|
||||||
avatarURL: APP.settings.getAvatarUrl(),
|
avatarURL: settings.avatarURL,
|
||||||
email: APP.settings.getEmail(),
|
email: settings.email,
|
||||||
name: APP.settings.getDisplayName()
|
name: settings.displayName
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// Profile is the new React compatible settings.
|
|
||||||
const profile = getState()['features/base/profile'];
|
|
||||||
|
|
||||||
if (profile) {
|
|
||||||
localParticipant.email
|
|
||||||
= profile.email || localParticipant.email;
|
|
||||||
localParticipant.name
|
|
||||||
= profile.displayName || localParticipant.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We set the initialized state here and not in the contructor to
|
// We set the initialized state here and not in the contructor to
|
||||||
// make sure that {@code componentWillMount} gets invoked before
|
// make sure that {@code componentWillMount} gets invoked before
|
||||||
|
@ -383,7 +373,7 @@ export class AbstractApp extends Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this.props.defaultURL
|
this.props.defaultURL
|
||||||
|| this._getStore().getState()['features/base/profile']
|
|| this._getStore().getState()['features/base/settings']
|
||||||
.serverURL
|
.serverURL
|
||||||
|| DEFAULT_URL);
|
|| DEFAULT_URL);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||||
|
import { updateSettings } from '../settings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get device id of the audio output device which is currently in use.
|
||||||
|
* Empty string stands for default device.
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function getAudioOutputDeviceId() {
|
||||||
|
return JitsiMeetJS.mediaDevices.getAudioOutputDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set device id of the audio output device which is currently in use.
|
||||||
|
* Empty string stands for default device.
|
||||||
|
*
|
||||||
|
* @param {string} newId - New audio output device id.
|
||||||
|
* @param {Function} dispatch - The Redux dispatch function.
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export function setAudioOutputDeviceId(
|
||||||
|
newId: string = 'default',
|
||||||
|
dispatch: Function): Promise<*> {
|
||||||
|
return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
|
||||||
|
.then(() =>
|
||||||
|
dispatch(updateSettings({
|
||||||
|
audioOutputDeviceId: newId
|
||||||
|
})));
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
export * from './actions';
|
export * from './actions';
|
||||||
export * from './actionTypes';
|
export * from './actionTypes';
|
||||||
|
export * from './functions';
|
||||||
|
|
||||||
import './middleware';
|
import './middleware';
|
||||||
import './reducer';
|
import './reducer';
|
||||||
|
|
|
@ -8,8 +8,8 @@ import {
|
||||||
} from '../../analytics';
|
} from '../../analytics';
|
||||||
import { isRoomValid, SET_ROOM, setAudioOnly } from '../conference';
|
import { isRoomValid, SET_ROOM, setAudioOnly } from '../conference';
|
||||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||||
import { getPropertyValue } from '../profile';
|
|
||||||
import { MiddlewareRegistry } from '../redux';
|
import { MiddlewareRegistry } from '../redux';
|
||||||
|
import { getPropertyValue } from '../settings';
|
||||||
import { setTrackMuted, TRACK_ADDED } from '../tracks';
|
import { setTrackMuted, TRACK_ADDED } from '../tracks';
|
||||||
|
|
||||||
import { setAudioMuted, setCameraFacingMode, setVideoMuted } from './actions';
|
import { setAudioMuted, setCameraFacingMode, setVideoMuted } from './actions';
|
||||||
|
@ -79,11 +79,11 @@ function _setRoom({ dispatch, getState }, next, action) {
|
||||||
const mutedSources = {
|
const mutedSources = {
|
||||||
// We have startWithAudioMuted and startWithVideoMuted here:
|
// We have startWithAudioMuted and startWithVideoMuted here:
|
||||||
config: true,
|
config: true,
|
||||||
profile: true,
|
settings: true,
|
||||||
|
|
||||||
// XXX We've already overwritten base/config with urlParams. However,
|
// XXX We've already overwritten base/config with urlParams. However,
|
||||||
// profile is more important than the server-side config. Consequently,
|
// settings are more important than the server-side config.
|
||||||
// we need to read from urlParams anyway:
|
// Consequently, we need to read from urlParams anyway:
|
||||||
urlParams: true,
|
urlParams: true,
|
||||||
|
|
||||||
// We don't have startWithAudioMuted and startWithVideoMuted here:
|
// We don't have startWithAudioMuted and startWithVideoMuted here:
|
||||||
|
@ -141,7 +141,7 @@ function _setRoom({ dispatch, getState }, next, action) {
|
||||||
config: roomIsValid,
|
config: roomIsValid,
|
||||||
|
|
||||||
// XXX We've already overwritten base/config with
|
// XXX We've already overwritten base/config with
|
||||||
// urlParams if roomIsValid. However, profile is more
|
// urlParams if roomIsValid. However, settings are more
|
||||||
// important than the server-side config. Consequently,
|
// important than the server-side config. Consequently,
|
||||||
// we need to read from urlParams anyway. We also
|
// we need to read from urlParams anyway. We also
|
||||||
// probably want to read from urlParams when
|
// probably want to read from urlParams when
|
||||||
|
@ -151,7 +151,7 @@ function _setRoom({ dispatch, getState }, next, action) {
|
||||||
// The following don't have complications around whether
|
// The following don't have complications around whether
|
||||||
// they are defined or not:
|
// they are defined or not:
|
||||||
jwt: false,
|
jwt: false,
|
||||||
profile: true
|
settings: true
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
// Default to audio-only if the (execution) environment does not
|
// Default to audio-only if the (execution) environment does not
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
/**
|
|
||||||
* Create an action for when the local profile is updated.
|
|
||||||
*
|
|
||||||
* {
|
|
||||||
* type: PROFILE_UPDATED,
|
|
||||||
* profile: {
|
|
||||||
* displayName: string,
|
|
||||||
* defaultURL: URL,
|
|
||||||
* email: string,
|
|
||||||
* startWithAudioMuted: boolean,
|
|
||||||
* startWithVideoMuted: boolean
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
export const PROFILE_UPDATED = Symbol('PROFILE_UPDATED');
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { PROFILE_UPDATED } from './actionTypes';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an action for when the local profile is updated.
|
|
||||||
*
|
|
||||||
* @param {Object} profile - The new profile data.
|
|
||||||
* @returns {{
|
|
||||||
* type: UPDATE_PROFILE,
|
|
||||||
* profile: {
|
|
||||||
* displayName: string,
|
|
||||||
* defaultURL: URL,
|
|
||||||
* email: string,
|
|
||||||
* startWithAudioMuted: boolean,
|
|
||||||
* startWithVideoMuted: boolean
|
|
||||||
* }
|
|
||||||
* }}
|
|
||||||
*/
|
|
||||||
export function updateProfile(profile) {
|
|
||||||
return {
|
|
||||||
type: PROFILE_UPDATED,
|
|
||||||
profile
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import { APP_WILL_MOUNT } from '../../app';
|
|
||||||
import { ReducerRegistry } from '../redux';
|
|
||||||
import { PersistenceRegistry } from '../storage';
|
|
||||||
|
|
||||||
import { PROFILE_UPDATED } from './actionTypes';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default/initial redux state of the feature {@code base/profile}.
|
|
||||||
*
|
|
||||||
* @type Object
|
|
||||||
*/
|
|
||||||
const DEFAULT_STATE = {};
|
|
||||||
|
|
||||||
const STORE_NAME = 'features/base/profile';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the persistence of the feature {@code base/profile}.
|
|
||||||
*/
|
|
||||||
PersistenceRegistry.register(STORE_NAME);
|
|
||||||
|
|
||||||
ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case APP_WILL_MOUNT:
|
|
||||||
// XXX APP_WILL_MOUNT is the earliest redux action of ours dispatched in
|
|
||||||
// the store. For the purposes of legacy support, make sure that the
|
|
||||||
// deserialized base/profile's state is in the format deemed current by
|
|
||||||
// the current app revision.
|
|
||||||
if (state && typeof state === 'object') {
|
|
||||||
// In an enterprise/internal build of Jitsi Meet for Android and iOS
|
|
||||||
// we had base/profile's state as an object with property profile.
|
|
||||||
const { profile } = state;
|
|
||||||
|
|
||||||
if (profile && typeof profile === 'object') {
|
|
||||||
return { ...profile };
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// In the weird case that we have previously persisted/serialized
|
|
||||||
// null.
|
|
||||||
return DEFAULT_STATE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROFILE_UPDATED:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
...action.profile
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return state;
|
|
||||||
});
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
* Create an action for when the settings are updated.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: SETTINGS_UPDATED,
|
||||||
|
* settings: {
|
||||||
|
* audioOutputDeviceId: string,
|
||||||
|
* avatarID: string,
|
||||||
|
* avatarURL: string,
|
||||||
|
* cameraDeviceId: string,
|
||||||
|
* displayName: string,
|
||||||
|
* email: string,
|
||||||
|
* localFlipX: boolean,
|
||||||
|
* micDeviceId: string,
|
||||||
|
* serverURL: string,
|
||||||
|
* startAudioOnly: boolean,
|
||||||
|
* startWithAudioMuted: boolean,
|
||||||
|
* startWithVideoMuted: boolean
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SETTINGS_UPDATED = Symbol('SETTINGS_UPDATED');
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { SETTINGS_UPDATED } from './actionTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an action for when the settings are updated.
|
||||||
|
*
|
||||||
|
* @param {Object} settings - The new (partial) settings properties.
|
||||||
|
* @returns {{
|
||||||
|
* type: SETTINGS_UPDATED,
|
||||||
|
* settings: {
|
||||||
|
* audioOutputDeviceId: string,
|
||||||
|
* avatarID: string,
|
||||||
|
* avatarURL: string,
|
||||||
|
* cameraDeviceId: string,
|
||||||
|
* displayName: string,
|
||||||
|
* email: string,
|
||||||
|
* localFlipX: boolean,
|
||||||
|
* micDeviceId: string,
|
||||||
|
* serverURL: string,
|
||||||
|
* startAudioOnly: boolean,
|
||||||
|
* startWithAudioMuted: boolean,
|
||||||
|
* startWithVideoMuted: boolean
|
||||||
|
* }
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function updateSettings(settings) {
|
||||||
|
return {
|
||||||
|
type: SETTINGS_UPDATED,
|
||||||
|
settings
|
||||||
|
};
|
||||||
|
}
|
|
@ -3,9 +3,11 @@
|
||||||
import { parseURLParams } from '../config';
|
import { parseURLParams } from '../config';
|
||||||
import { toState } from '../redux';
|
import { toState } from '../redux';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the effective value of a configuration/preference/setting by applying
|
* Returns the effective value of a configuration/preference/setting by applying
|
||||||
* a precedence among the values specified by JWT, URL, profile, and config.
|
* a precedence among the values specified by JWT, URL, settings,
|
||||||
|
* and config.
|
||||||
*
|
*
|
||||||
* @param {Object|Function} stateful - The redux state object or
|
* @param {Object|Function} stateful - The redux state object or
|
||||||
* {@code getState} function.
|
* {@code getState} function.
|
||||||
|
@ -14,7 +16,7 @@ import { toState } from '../redux';
|
||||||
* @param {{
|
* @param {{
|
||||||
* config: boolean,
|
* config: boolean,
|
||||||
* jwt: boolean,
|
* jwt: boolean,
|
||||||
* profile: boolean,
|
* settings: boolean,
|
||||||
* urlParams: boolean
|
* urlParams: boolean
|
||||||
* }} [sources] - A set/structure of {@code boolean} flags indicating the
|
* }} [sources] - A set/structure of {@code boolean} flags indicating the
|
||||||
* configuration/preference/setting sources to consider/retrieve values from.
|
* configuration/preference/setting sources to consider/retrieve values from.
|
||||||
|
@ -31,13 +33,13 @@ export function getPropertyValue(
|
||||||
// Defaults:
|
// Defaults:
|
||||||
config: true,
|
config: true,
|
||||||
jwt: true,
|
jwt: true,
|
||||||
profile: true,
|
settings: true,
|
||||||
urlParams: true,
|
urlParams: true,
|
||||||
|
|
||||||
...sources
|
...sources
|
||||||
};
|
};
|
||||||
|
|
||||||
// Precedence: jwt -> urlParams -> profile -> config.
|
// Precedence: jwt -> urlParams -> settings -> config.
|
||||||
|
|
||||||
const state = toState(stateful);
|
const state = toState(stateful);
|
||||||
|
|
||||||
|
@ -61,9 +63,9 @@ export function getPropertyValue(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// profile
|
// settings
|
||||||
if (sources.profile) {
|
if (sources.settings) {
|
||||||
const value = state['features/base/profile'][propertyName];
|
const value = state['features/base/settings'][propertyName];
|
||||||
|
|
||||||
if (typeof value !== 'undefined') {
|
if (typeof value !== 'undefined') {
|
||||||
return value;
|
return value;
|
|
@ -4,12 +4,13 @@ import { setAudioOnly } from '../conference';
|
||||||
import { getLocalParticipant, participantUpdated } from '../participants';
|
import { getLocalParticipant, participantUpdated } from '../participants';
|
||||||
import { MiddlewareRegistry, toState } from '../redux';
|
import { MiddlewareRegistry, toState } from '../redux';
|
||||||
|
|
||||||
import { PROFILE_UPDATED } from './actionTypes';
|
import { SETTINGS_UPDATED } from './actionTypes';
|
||||||
|
import { getSettings } from './functions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The middleware of the feature base/profile. Distributes changes to the state
|
* The middleware of the feature base/settings. Distributes changes to the state
|
||||||
* of base/profile to the states of other features computed from the state of
|
* of base/settings to the states of other features computed from the state of
|
||||||
* base/profile.
|
* base/settings.
|
||||||
*
|
*
|
||||||
* @param {Store} store - The redux store.
|
* @param {Store} store - The redux store.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
|
@ -18,16 +19,16 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case PROFILE_UPDATED:
|
case SETTINGS_UPDATED:
|
||||||
_updateLocalParticipant(store);
|
|
||||||
_maybeSetAudioOnly(store, action);
|
_maybeSetAudioOnly(store, action);
|
||||||
|
_updateLocalParticipant(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates {@code startAudioOnly} flag if it's updated in the profile.
|
* Updates {@code startAudioOnly} flag if it's updated in the settings.
|
||||||
*
|
*
|
||||||
* @param {Store} store - The redux store.
|
* @param {Store} store - The redux store.
|
||||||
* @param {Object} action - The redux action.
|
* @param {Object} action - The redux action.
|
||||||
|
@ -36,14 +37,14 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
*/
|
*/
|
||||||
function _maybeSetAudioOnly(
|
function _maybeSetAudioOnly(
|
||||||
{ dispatch },
|
{ dispatch },
|
||||||
{ profile: { startAudioOnly } }) {
|
{ settings: { startAudioOnly } }) {
|
||||||
if (typeof startAudioOnly === 'boolean') {
|
if (typeof startAudioOnly === 'boolean') {
|
||||||
dispatch(setAudioOnly(startAudioOnly));
|
dispatch(setAudioOnly(startAudioOnly));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the local participant according to profile changes.
|
* Updates the local participant according to settings changes.
|
||||||
*
|
*
|
||||||
* @param {Store} store - The redux store.
|
* @param {Store} store - The redux store.
|
||||||
* @private
|
* @private
|
||||||
|
@ -52,7 +53,7 @@ function _maybeSetAudioOnly(
|
||||||
function _updateLocalParticipant(store) {
|
function _updateLocalParticipant(store) {
|
||||||
const state = toState(store);
|
const state = toState(store);
|
||||||
const localParticipant = getLocalParticipant(state);
|
const localParticipant = getLocalParticipant(state);
|
||||||
const profile = state['features/base/profile'];
|
const settings = getSettings(state);
|
||||||
|
|
||||||
store.dispatch(participantUpdated({
|
store.dispatch(participantUpdated({
|
||||||
// Identify that the participant to update i.e. the local participant:
|
// Identify that the participant to update i.e. the local participant:
|
||||||
|
@ -60,7 +61,7 @@ function _updateLocalParticipant(store) {
|
||||||
local: true,
|
local: true,
|
||||||
|
|
||||||
// Specify the updates to be applied to the identified participant:
|
// Specify the updates to be applied to the identified participant:
|
||||||
email: profile.email,
|
email: settings.email,
|
||||||
name: profile.displayName
|
name: settings.displayName
|
||||||
}));
|
}));
|
||||||
}
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
// @flow
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { APP_WILL_MOUNT } from '../../app';
|
||||||
|
|
||||||
|
import JitsiMeetJS, { browser } from '../lib-jitsi-meet';
|
||||||
|
import { ReducerRegistry } from '../redux';
|
||||||
|
import { PersistenceRegistry } from '../storage';
|
||||||
|
import { assignIfDefined, randomHexString } from '../util';
|
||||||
|
|
||||||
|
import { SETTINGS_UPDATED } from './actionTypes';
|
||||||
|
|
||||||
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default/initial redux state of the feature {@code base/settings}.
|
||||||
|
*
|
||||||
|
* @type Object
|
||||||
|
*/
|
||||||
|
const DEFAULT_STATE = {
|
||||||
|
audioOutputDeviceId: undefined,
|
||||||
|
avatarID: undefined,
|
||||||
|
avatarURL: undefined,
|
||||||
|
cameraDeviceId: undefined,
|
||||||
|
displayName: undefined,
|
||||||
|
email: undefined,
|
||||||
|
localFlipX: true,
|
||||||
|
micDeviceId: undefined,
|
||||||
|
serverURL: undefined,
|
||||||
|
startAudioOnly: false,
|
||||||
|
startWithAudioMuted: false,
|
||||||
|
startWithVideoMuted: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const STORE_NAME = 'features/base/settings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the persistence of the feature {@code base/settings}.
|
||||||
|
*/
|
||||||
|
PersistenceRegistry.register(STORE_NAME);
|
||||||
|
|
||||||
|
ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case APP_WILL_MOUNT:
|
||||||
|
return _initSettings(state);
|
||||||
|
|
||||||
|
case SETTINGS_UPDATED:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...action.settings
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the legacy profile values regardless of it's being in pre or
|
||||||
|
* post-flattening format.
|
||||||
|
*
|
||||||
|
* FIXME: Let's remove this after a predefined time (e.g. by July 2018) to avoid
|
||||||
|
* garbage in the source.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
function _getLegacyProfile() {
|
||||||
|
let persistedProfile
|
||||||
|
= window.localStorage.getItem('features/base/profile');
|
||||||
|
|
||||||
|
if (persistedProfile) {
|
||||||
|
try {
|
||||||
|
persistedProfile = JSON.parse(persistedProfile);
|
||||||
|
|
||||||
|
if (persistedProfile && typeof persistedProfile === 'object') {
|
||||||
|
const preFlattenedProfile = persistedProfile.profile;
|
||||||
|
|
||||||
|
return preFlattenedProfile || persistedProfile;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn('Error parsing persisted legacy profile', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inits the settings object based on what information we have available.
|
||||||
|
* Info taken into consideration:
|
||||||
|
* - Old Settings.js style data
|
||||||
|
* - Things that we stored in profile earlier but belong here.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object} featureState - The current state of the feature.
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
function _initSettings(featureState) {
|
||||||
|
let settings = featureState;
|
||||||
|
|
||||||
|
// Old Settings.js values
|
||||||
|
// FIXME: Let's remove this after a predefined time (e.g. by July 2018) to
|
||||||
|
// avoid garbage in the source.
|
||||||
|
const displayName = _.escape(window.localStorage.getItem('displayname'));
|
||||||
|
const email = _.escape(window.localStorage.getItem('email'));
|
||||||
|
let avatarID = _.escape(window.localStorage.getItem('avatarId'));
|
||||||
|
|
||||||
|
if (!avatarID) {
|
||||||
|
// if there is no avatar id, we generate a unique one and use it forever
|
||||||
|
avatarID = randomHexString(32);
|
||||||
|
}
|
||||||
|
|
||||||
|
settings = assignIfDefined({
|
||||||
|
avatarID,
|
||||||
|
displayName,
|
||||||
|
email
|
||||||
|
}, settings);
|
||||||
|
|
||||||
|
if (!browser.isReactNative()) {
|
||||||
|
// Browser only
|
||||||
|
const localFlipX
|
||||||
|
= JSON.parse(window.localStorage.getItem('localFlipX') || 'true');
|
||||||
|
const cameraDeviceId
|
||||||
|
= window.localStorage.getItem('cameraDeviceId') || '';
|
||||||
|
const micDeviceId = window.localStorage.getItem('micDeviceId') || '';
|
||||||
|
|
||||||
|
// Currently audio output device change is supported only in Chrome and
|
||||||
|
// default output always has 'default' device ID
|
||||||
|
const audioOutputDeviceId
|
||||||
|
= window.localStorage.getItem('audioOutputDeviceId') || 'default';
|
||||||
|
|
||||||
|
if (audioOutputDeviceId
|
||||||
|
!== JitsiMeetJS.mediaDevices.getAudioOutputDevice()) {
|
||||||
|
JitsiMeetJS.mediaDevices.setAudioOutputDevice(
|
||||||
|
audioOutputDeviceId
|
||||||
|
).catch(ex => {
|
||||||
|
logger.warn('Failed to set audio output device from local '
|
||||||
|
+ 'storage. Default audio output device will be used'
|
||||||
|
+ 'instead.', ex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
settings = assignIfDefined({
|
||||||
|
audioOutputDeviceId,
|
||||||
|
cameraDeviceId,
|
||||||
|
localFlipX,
|
||||||
|
micDeviceId
|
||||||
|
}, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Things we stored in profile earlier
|
||||||
|
const legacyProfile = _getLegacyProfile();
|
||||||
|
|
||||||
|
settings = assignIfDefined(legacyProfile, settings);
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
|
@ -6,18 +6,20 @@ store/state into window.localStorage (on Web) or AsyncStorage (on mobile).
|
||||||
Usage
|
Usage
|
||||||
=====
|
=====
|
||||||
If a subtree of the redux store should be persisted (e.g.
|
If a subtree of the redux store should be persisted (e.g.
|
||||||
`'features/base/profile'`), then persistence for that subtree should be
|
`'features/base/settings'`), then persistence for that subtree should be
|
||||||
requested by registering the subtree with `PersistenceRegistry`.
|
requested by registering the subtree with `PersistenceRegistry`.
|
||||||
|
|
||||||
For example, to register the field `profile` of the redux subtree
|
For example, to register the field `displayName` of the redux subtree
|
||||||
`'features/base/profile'` to be persisted, use:
|
`'features/base/settings'` to be persisted, use:
|
||||||
```javascript
|
```javascript
|
||||||
PersistenceRegistry.register('features/base/profile', {
|
PersistenceRegistry.register('features/base/settings', {
|
||||||
profile: true
|
displayName: true
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
in the `reducer.js` of the `base/profile` feature.
|
in the `reducer.js` of the `base/settings` feature.
|
||||||
|
|
||||||
|
If the second parameter is omitted, the entire feature state is persisted.
|
||||||
|
|
||||||
When it's done, Jitsi Meet will automatically persist these subtrees and
|
When it's done, Jitsi Meet will automatically persist these subtrees and
|
||||||
rehydrate them on startup.
|
rehydrate them on startup.
|
||||||
|
@ -31,3 +33,12 @@ throttling timeout can be configured in
|
||||||
```
|
```
|
||||||
react/features/base/storage/middleware.js#PERSIST_STATE_DELAY
|
react/features/base/storage/middleware.js#PERSIST_STATE_DELAY
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Serialization
|
||||||
|
=============
|
||||||
|
The API JSON.stringify() is currently used to serialize feature states,
|
||||||
|
therefore its limitations affect the persistency feature too. E.g. complex
|
||||||
|
objects, such as Maps (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)
|
||||||
|
or Sets (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set)
|
||||||
|
cannot be automatically persisted at the moment. The same applies to Functions
|
||||||
|
(which is not a good practice to store in Redux anyhow).
|
||||||
|
|
|
@ -35,14 +35,16 @@ export function createLocalTracksF(
|
||||||
if (typeof APP !== 'undefined') {
|
if (typeof APP !== 'undefined') {
|
||||||
// TODO The app's settings should go in the redux store and then the
|
// TODO The app's settings should go in the redux store and then the
|
||||||
// reliance on the global variable APP will go away.
|
// reliance on the global variable APP will go away.
|
||||||
|
store || (store = APP.store); // eslint-disable-line no-param-reassign
|
||||||
|
|
||||||
|
const settings = store.getState()['features/base/settings'];
|
||||||
|
|
||||||
if (typeof cameraDeviceId === 'undefined' || cameraDeviceId === null) {
|
if (typeof cameraDeviceId === 'undefined' || cameraDeviceId === null) {
|
||||||
cameraDeviceId = APP.settings.getCameraDeviceId();
|
cameraDeviceId = settings.cameraDeviceId;
|
||||||
}
|
}
|
||||||
if (typeof micDeviceId === 'undefined' || micDeviceId === null) {
|
if (typeof micDeviceId === 'undefined' || micDeviceId === null) {
|
||||||
micDeviceId = APP.settings.getMicDeviceId();
|
micDeviceId = settings.micDeviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
store || (store = APP.store); // eslint-disable-line no-param-reassign
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -18,3 +18,27 @@ export function getJitsiMeetGlobalNS() {
|
||||||
|
|
||||||
return window.JitsiMeetJS.app;
|
return window.JitsiMeetJS.app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper function that behaves similar to Object.assign, but only reassigns a
|
||||||
|
* property in target if it's defined in source.
|
||||||
|
*
|
||||||
|
* @param {Object} target - The target object to assign the values into.
|
||||||
|
* @param {Object} source - The source object.
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
export function assignIfDefined(target: Object, source: Object) {
|
||||||
|
const to = Object(target);
|
||||||
|
|
||||||
|
for (const nextKey in source) {
|
||||||
|
if (source.hasOwnProperty(nextKey)) {
|
||||||
|
const value = source[nextKey];
|
||||||
|
|
||||||
|
if (typeof value !== 'undefined') {
|
||||||
|
to[nextKey] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
/* globals APP, interfaceConfig */
|
/* globals APP, interfaceConfig */
|
||||||
|
|
||||||
import { openDialog } from '../base/dialog';
|
|
||||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
|
||||||
import { API_ID } from '../../../modules/API/constants';
|
import { API_ID } from '../../../modules/API/constants';
|
||||||
import {
|
|
||||||
setAudioInputDevice,
|
|
||||||
setAudioOutputDevice,
|
|
||||||
setVideoInputDevice
|
|
||||||
} from '../base/devices';
|
|
||||||
import { i18next } from '../base/i18n';
|
|
||||||
import {
|
import {
|
||||||
PostMessageTransportBackend,
|
PostMessageTransportBackend,
|
||||||
Transport
|
Transport
|
||||||
} from '../../../modules/transport';
|
} from '../../../modules/transport';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getAudioOutputDeviceId,
|
||||||
|
setAudioInputDevice,
|
||||||
|
setAudioOutputDevice,
|
||||||
|
setVideoInputDevice
|
||||||
|
} from '../base/devices';
|
||||||
|
import { openDialog } from '../base/dialog';
|
||||||
|
import { i18next } from '../base/i18n';
|
||||||
|
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||||
|
|
||||||
import { SET_DEVICE_SELECTION_POPUP_DATA } from './actionTypes';
|
import { SET_DEVICE_SELECTION_POPUP_DATA } from './actionTypes';
|
||||||
import { DeviceSelectionDialog } from './components';
|
import { DeviceSelectionDialog } from './components';
|
||||||
|
|
||||||
|
@ -42,10 +44,12 @@ function _openDeviceSelectionDialogHere() {
|
||||||
return dispatch =>
|
return dispatch =>
|
||||||
JitsiMeetJS.mediaDevices.isDeviceListAvailable()
|
JitsiMeetJS.mediaDevices.isDeviceListAvailable()
|
||||||
.then(isDeviceListAvailable => {
|
.then(isDeviceListAvailable => {
|
||||||
|
const settings = APP.store.getState()['features/base/settings'];
|
||||||
|
|
||||||
dispatch(openDialog(DeviceSelectionDialog, {
|
dispatch(openDialog(DeviceSelectionDialog, {
|
||||||
currentAudioInputId: APP.settings.getMicDeviceId(),
|
currentAudioInputId: settings.micDeviceId,
|
||||||
currentAudioOutputId: APP.settings.getAudioOutputDeviceId(),
|
currentAudioOutputId: getAudioOutputDeviceId(),
|
||||||
currentVideoInputId: APP.settings.getCameraDeviceId(),
|
currentVideoInputId: settings.cameraDeviceId,
|
||||||
disableAudioInputChange:
|
disableAudioInputChange:
|
||||||
!JitsiMeetJS.isMultipleAudioInputSupported(),
|
!JitsiMeetJS.isMultipleAudioInputSupported(),
|
||||||
disableDeviceChange: !isDeviceListAvailable
|
disableDeviceChange: !isDeviceListAvailable
|
||||||
|
@ -135,6 +139,9 @@ function _openDeviceSelectionDialogInPopup() {
|
||||||
*/
|
*/
|
||||||
function _processRequest(dispatch, getState, request, responseCallback) { // eslint-disable-line max-len, max-params
|
function _processRequest(dispatch, getState, request, responseCallback) { // eslint-disable-line max-len, max-params
|
||||||
if (request.type === 'devices') {
|
if (request.type === 'devices') {
|
||||||
|
const state = getState();
|
||||||
|
const settings = state['features/base/settings'];
|
||||||
|
|
||||||
switch (request.name) {
|
switch (request.name) {
|
||||||
case 'isDeviceListAvailable':
|
case 'isDeviceListAvailable':
|
||||||
JitsiMeetJS.mediaDevices.isDeviceListAvailable()
|
JitsiMeetJS.mediaDevices.isDeviceListAvailable()
|
||||||
|
@ -152,9 +159,9 @@ function _processRequest(dispatch, getState, request, responseCallback) { // esl
|
||||||
break;
|
break;
|
||||||
case 'getCurrentDevices':
|
case 'getCurrentDevices':
|
||||||
responseCallback({
|
responseCallback({
|
||||||
audioInput: APP.settings.getMicDeviceId(),
|
audioInput: settings.micDeviceId,
|
||||||
audioOutput: APP.settings.getAudioOutputDeviceId(),
|
audioOutput: getAudioOutputDeviceId(),
|
||||||
videoInput: APP.settings.getCameraDeviceId()
|
videoInput: settings.cameraDeviceId
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'getAvailableDevices':
|
case 'getAvailableDevices':
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
|
|
||||||
import { updateProfile } from '../../base/profile';
|
import { updateSettings } from '../../base/settings';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@code Component} props of
|
* The type of the React {@code Component} props of
|
||||||
|
@ -11,19 +11,17 @@ import { updateProfile } from '../../base/profile';
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current profile object.
|
* The default URL for when there is no custom URL set in the settings.
|
||||||
*
|
|
||||||
* @protected
|
|
||||||
*/
|
|
||||||
_profile: Object,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default URL for when there is no custom URL set in the profile.
|
|
||||||
*
|
*
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
_serverURL: string,
|
_serverURL: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current settings object.
|
||||||
|
*/
|
||||||
|
_settings: Object,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether {@link AbstractSettingsView} is visible.
|
* Whether {@link AbstractSettingsView} is visible.
|
||||||
*
|
*
|
||||||
|
@ -79,7 +77,7 @@ export class AbstractSettingsView extends Component<Props> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onChangeDisplayName(text) {
|
_onChangeDisplayName(text) {
|
||||||
this._updateProfile({
|
this._updateSettings({
|
||||||
displayName: text
|
displayName: text
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -94,7 +92,7 @@ export class AbstractSettingsView extends Component<Props> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onChangeEmail(text) {
|
_onChangeEmail(text) {
|
||||||
this._updateProfile({
|
this._updateSettings({
|
||||||
email: text
|
email: text
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -109,7 +107,7 @@ export class AbstractSettingsView extends Component<Props> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onChangeServerURL(text) {
|
_onChangeServerURL(text) {
|
||||||
this._updateProfile({
|
this._updateSettings({
|
||||||
serverURL: text
|
serverURL: text
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -125,7 +123,7 @@ export class AbstractSettingsView extends Component<Props> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onStartAudioMutedChange(newValue) {
|
_onStartAudioMutedChange(newValue) {
|
||||||
this._updateProfile({
|
this._updateSettings({
|
||||||
startWithAudioMuted: newValue
|
startWithAudioMuted: newValue
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -141,22 +139,23 @@ export class AbstractSettingsView extends Component<Props> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onStartVideoMutedChange(newValue) {
|
_onStartVideoMutedChange(newValue) {
|
||||||
this._updateProfile({
|
this._updateSettings({
|
||||||
startWithVideoMuted: newValue
|
startWithVideoMuted: newValue
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateProfile: (Object) => void;
|
_updateSettings: (Object) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the persisted profile on any change.
|
* Updates the persisted settings on any change.
|
||||||
*
|
*
|
||||||
* @param {Object} updateObject - The partial update object for the profile.
|
* @param {Object} updateObject - The partial update object for the
|
||||||
|
* settings.
|
||||||
* @private
|
* @private
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_updateProfile(updateObject: Object) {
|
_updateSettings(updateObject: Object) {
|
||||||
this.props.dispatch(updateProfile(updateObject));
|
this.props.dispatch(updateSettings(updateObject));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,15 +166,15 @@ export class AbstractSettingsView extends Component<Props> {
|
||||||
* @param {Object} state - The redux state.
|
* @param {Object} state - The redux state.
|
||||||
* @protected
|
* @protected
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* _profile: Object,
|
|
||||||
* _serverURL: string,
|
* _serverURL: string,
|
||||||
|
* _settings: Object,
|
||||||
* _visible: boolean
|
* _visible: boolean
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function _mapStateToProps(state: Object) {
|
export function _mapStateToProps(state: Object) {
|
||||||
return {
|
return {
|
||||||
_profile: state['features/base/profile'],
|
|
||||||
_serverURL: state['features/app'].app._getDefaultURL(),
|
_serverURL: state['features/app'].app._getDefaultURL(),
|
||||||
|
_settings: state['features/base/settings'],
|
||||||
_visible: state['features/settings'].visible
|
_visible: state['features/settings'].visible
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,7 @@ class SettingsView extends AbstractSettingsView {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_processServerURL(hideOnSuccess: boolean) {
|
_processServerURL(hideOnSuccess: boolean) {
|
||||||
const { serverURL } = this.props._profile;
|
const { serverURL } = this.props._settings;
|
||||||
const normalizedURL = normalizeUserInputURL(serverURL);
|
const normalizedURL = normalizeUserInputURL(serverURL);
|
||||||
|
|
||||||
if (normalizedURL === null) {
|
if (normalizedURL === null) {
|
||||||
|
@ -140,7 +140,7 @@ class SettingsView extends AbstractSettingsView {
|
||||||
* @returns {React$Element}
|
* @returns {React$Element}
|
||||||
*/
|
*/
|
||||||
_renderBody() {
|
_renderBody() {
|
||||||
const { _profile } = this.props;
|
const { _settings } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style = { styles.settingsForm }>
|
<SafeAreaView style = { styles.settingsForm }>
|
||||||
|
@ -154,7 +154,7 @@ class SettingsView extends AbstractSettingsView {
|
||||||
autoCorrect = { false }
|
autoCorrect = { false }
|
||||||
onChangeText = { this._onChangeDisplayName }
|
onChangeText = { this._onChangeDisplayName }
|
||||||
placeholder = 'John Doe'
|
placeholder = 'John Doe'
|
||||||
value = { _profile.displayName } />
|
value = { _settings.displayName } />
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormRow i18nLabel = 'settingsView.email'>
|
<FormRow i18nLabel = 'settingsView.email'>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
@ -163,7 +163,7 @@ class SettingsView extends AbstractSettingsView {
|
||||||
keyboardType = { 'email-address' }
|
keyboardType = { 'email-address' }
|
||||||
onChangeText = { this._onChangeEmail }
|
onChangeText = { this._onChangeEmail }
|
||||||
placeholder = 'email@example.com'
|
placeholder = 'email@example.com'
|
||||||
value = { _profile.email } />
|
value = { _settings.email } />
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormSectionHeader
|
<FormSectionHeader
|
||||||
i18nLabel = 'settingsView.conferenceSection' />
|
i18nLabel = 'settingsView.conferenceSection' />
|
||||||
|
@ -176,19 +176,19 @@ class SettingsView extends AbstractSettingsView {
|
||||||
onBlur = { this._onBlurServerURL }
|
onBlur = { this._onBlurServerURL }
|
||||||
onChangeText = { this._onChangeServerURL }
|
onChangeText = { this._onChangeServerURL }
|
||||||
placeholder = { this.props._serverURL }
|
placeholder = { this.props._serverURL }
|
||||||
value = { _profile.serverURL } />
|
value = { _settings.serverURL } />
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormRow
|
<FormRow
|
||||||
fieldSeparator = { true }
|
fieldSeparator = { true }
|
||||||
i18nLabel = 'settingsView.startWithAudioMuted'>
|
i18nLabel = 'settingsView.startWithAudioMuted'>
|
||||||
<Switch
|
<Switch
|
||||||
onValueChange = { this._onStartAudioMutedChange }
|
onValueChange = { this._onStartAudioMutedChange }
|
||||||
value = { _profile.startWithAudioMuted } />
|
value = { _settings.startWithAudioMuted } />
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormRow i18nLabel = 'settingsView.startWithVideoMuted'>
|
<FormRow i18nLabel = 'settingsView.startWithVideoMuted'>
|
||||||
<Switch
|
<Switch
|
||||||
onValueChange = { this._onStartVideoMutedChange }
|
onValueChange = { this._onStartVideoMutedChange }
|
||||||
value = { _profile.startWithVideoMuted } />
|
value = { _settings.startWithVideoMuted } />
|
||||||
</FormRow>
|
</FormRow>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
|
|
@ -14,10 +14,18 @@ import { generateRoomWithoutSeparator } from '../functions';
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user's profile.
|
* Room name to join to.
|
||||||
*/
|
*/
|
||||||
_profile: Object,
|
|
||||||
_room: string,
|
_room: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current settings.
|
||||||
|
*/
|
||||||
|
_settings: Object,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Redux dispatch Function.
|
||||||
|
*/
|
||||||
dispatch: Dispatch<*>
|
dispatch: Dispatch<*>
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -229,13 +237,13 @@ export class AbstractWelcomePage extends Component<Props, *> {
|
||||||
* @param {Object} state - The redux state.
|
* @param {Object} state - The redux state.
|
||||||
* @protected
|
* @protected
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* _profile: Object,
|
* _room: string,
|
||||||
* _room: string
|
* _settings: Object
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function _mapStateToProps(state: Object) {
|
export function _mapStateToProps(state: Object) {
|
||||||
return {
|
return {
|
||||||
_profile: state['features/base/profile'],
|
_room: state['features/base/conference'].room,
|
||||||
_room: state['features/base/conference'].room
|
_settings: state['features/base/settings']
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import { Switch, TouchableWithoutFeedback, View } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { translate } from '../../base/i18n';
|
import { translate } from '../../base/i18n';
|
||||||
import { updateProfile } from '../../base/profile';
|
|
||||||
import { Header, Text } from '../../base/react';
|
import { Header, Text } from '../../base/react';
|
||||||
|
import { updateSettings } from '../../base/settings';
|
||||||
|
|
||||||
import styles, { SWITCH_THUMB_COLOR, SWITCH_UNDER_COLOR } from './styles';
|
import styles, { SWITCH_THUMB_COLOR, SWITCH_UNDER_COLOR } from './styles';
|
||||||
|
|
||||||
|
@ -26,9 +26,9 @@ type Props = {
|
||||||
t: Function,
|
t: Function,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current profile settings from redux.
|
* The current settings from redux.
|
||||||
*/
|
*/
|
||||||
_profile: Object
|
_settings: Object
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,7 +55,7 @@ class VideoSwitch extends Component<Props> {
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { t, _profile } = this.props;
|
const { t, _settings } = this.props;
|
||||||
const { textStyle } = Header;
|
const { textStyle } = Header;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -71,7 +71,7 @@ class VideoSwitch extends Component<Props> {
|
||||||
onValueChange = { this._onStartAudioOnlyChange }
|
onValueChange = { this._onStartAudioOnlyChange }
|
||||||
style = { styles.audioVideoSwitch }
|
style = { styles.audioVideoSwitch }
|
||||||
thumbTintColor = { SWITCH_THUMB_COLOR }
|
thumbTintColor = { SWITCH_THUMB_COLOR }
|
||||||
value = { _profile.startAudioOnly } />
|
value = { _settings.startAudioOnly } />
|
||||||
<TouchableWithoutFeedback
|
<TouchableWithoutFeedback
|
||||||
onPress = { this._onStartAudioOnlyTrue }>
|
onPress = { this._onStartAudioOnlyTrue }>
|
||||||
<Text style = { textStyle }>
|
<Text style = { textStyle }>
|
||||||
|
@ -94,8 +94,7 @@ class VideoSwitch extends Component<Props> {
|
||||||
_onStartAudioOnlyChange(startAudioOnly) {
|
_onStartAudioOnlyChange(startAudioOnly) {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
|
|
||||||
dispatch(updateProfile({
|
dispatch(updateSettings({
|
||||||
...this.props._profile,
|
|
||||||
startAudioOnly
|
startAudioOnly
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -124,12 +123,12 @@ class VideoSwitch extends Component<Props> {
|
||||||
* @param {Object} state - The redux state.
|
* @param {Object} state - The redux state.
|
||||||
* @protected
|
* @protected
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* _profile: Object
|
* _settings: Object
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function _mapStateToProps(state: Object) {
|
export function _mapStateToProps(state: Object) {
|
||||||
return {
|
return {
|
||||||
_profile: state['features/base/profile']
|
_settings: state['features/base/settings']
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ class WelcomePage extends AbstractWelcomePage {
|
||||||
|
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
|
|
||||||
if (this.props._profile.startAudioOnly) {
|
if (this.props._settings.startAudioOnly) {
|
||||||
dispatch(destroyLocalTracks());
|
dispatch(destroyLocalTracks());
|
||||||
} else {
|
} else {
|
||||||
dispatch(createDesiredLocalTracks(MEDIA_TYPE.VIDEO));
|
dispatch(createDesiredLocalTracks(MEDIA_TYPE.VIDEO));
|
||||||
|
|
Loading…
Reference in New Issue