Merge pull request #3009 from jitsi/state-listener
[RN] Prevent multiplying remote thumbnails
This commit is contained in:
commit
480fe53001
|
@ -1688,6 +1688,7 @@ export default {
|
|||
const displayName = user.getDisplayName();
|
||||
|
||||
APP.store.dispatch(participantJoined({
|
||||
conference: room,
|
||||
id,
|
||||
name: displayName,
|
||||
role: user.getRole()
|
||||
|
@ -1709,7 +1710,7 @@ export default {
|
|||
if (user.isHidden()) {
|
||||
return;
|
||||
}
|
||||
APP.store.dispatch(participantLeft(id, user));
|
||||
APP.store.dispatch(participantLeft(id, room));
|
||||
logger.log('USER %s LEFT', id, user);
|
||||
APP.API.notifyUserLeft(id);
|
||||
APP.UI.removeUser(id, user.getDisplayName());
|
||||
|
@ -1803,9 +1804,9 @@ export default {
|
|||
|
||||
APP.UI.participantConnectionStatusChanged(id);
|
||||
});
|
||||
room.on(JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED, id => {
|
||||
APP.store.dispatch(dominantSpeakerChanged(id));
|
||||
});
|
||||
room.on(
|
||||
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
|
||||
id => APP.store.dispatch(dominantSpeakerChanged(id, room)));
|
||||
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
||||
|
@ -1882,6 +1883,7 @@ export default {
|
|||
= displayName.substr(0, MAX_DISPLAY_NAME_LENGTH);
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id,
|
||||
name: formattedDisplayName
|
||||
}));
|
||||
|
@ -1915,6 +1917,7 @@ export default {
|
|||
switch (name) {
|
||||
case 'raisedHand':
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id: participant.getId(),
|
||||
raisedHand: newValue === 'true'
|
||||
}));
|
||||
|
@ -2014,6 +2017,7 @@ export default {
|
|||
APP.UI.addListener(UIEvents.EMAIL_CHANGED, this.changeLocalEmail);
|
||||
room.addCommandListener(this.commands.defaults.EMAIL, (data, from) => {
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id: from,
|
||||
email: data.value
|
||||
}));
|
||||
|
@ -2025,6 +2029,7 @@ export default {
|
|||
(data, from) => {
|
||||
APP.store.dispatch(
|
||||
participantUpdated({
|
||||
conference: room,
|
||||
id: from,
|
||||
avatarURL: data.value
|
||||
}));
|
||||
|
@ -2034,6 +2039,7 @@ export default {
|
|||
(data, from) => {
|
||||
APP.store.dispatch(
|
||||
participantUpdated({
|
||||
conference: room,
|
||||
id: from,
|
||||
avatarID: data.value
|
||||
}));
|
||||
|
@ -2578,6 +2584,12 @@ export default {
|
|||
const localId = localParticipant.id;
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
|
||||
id: localId,
|
||||
local: true,
|
||||
email: formattedEmail
|
||||
|
@ -2605,6 +2617,12 @@ export default {
|
|||
}
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
|
||||
id,
|
||||
local: true,
|
||||
avatarURL: formattedUrl
|
||||
|
@ -2661,6 +2679,12 @@ export default {
|
|||
}
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
|
||||
id,
|
||||
local: true,
|
||||
name: formattedNickname
|
||||
|
|
|
@ -306,6 +306,7 @@ export default class SharedVideoManager {
|
|||
SHARED_VIDEO_CONTAINER_TYPE, self.sharedVideo);
|
||||
|
||||
APP.store.dispatch(participantJoined({
|
||||
conference: APP.conference,
|
||||
id: self.url,
|
||||
isBot: true,
|
||||
name: 'YouTube'
|
||||
|
@ -516,7 +517,7 @@ export default class SharedVideoManager {
|
|||
UIEvents.UPDATE_SHARED_VIDEO, null, 'removed');
|
||||
});
|
||||
|
||||
APP.store.dispatch(participantLeft(this.url));
|
||||
APP.store.dispatch(participantLeft(this.url, APP.conference));
|
||||
|
||||
this.url = null;
|
||||
this.isSharedVideoShown = false;
|
||||
|
|
|
@ -10556,13 +10556,13 @@
|
|||
}
|
||||
},
|
||||
"react-redux": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.6.tgz",
|
||||
"integrity": "sha512-8taaaGu+J7PMJQDJrk/xiWEYQmdo3mkXw6wPr3K3LxvXis3Fymiq7c13S+Tpls/AyNUAsoONkU81AP0RA6y6Vw==",
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz",
|
||||
"integrity": "sha512-5VI8EV5hdgNgyjfmWzBbdrqUkrVRKlyTKk1sGH3jzM2M2Mhj/seQgPXaz6gVAj2lz/nz688AdTqMO18Lr24Zhg==",
|
||||
"requires": {
|
||||
"hoist-non-react-statics": "2.5.0",
|
||||
"invariant": "2.2.3",
|
||||
"lodash": "4.17.4",
|
||||
"lodash": "4.17.10",
|
||||
"lodash-es": "4.17.5",
|
||||
"loose-envify": "1.3.1",
|
||||
"prop-types": "15.6.0"
|
||||
|
@ -10572,6 +10572,11 @@
|
|||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz",
|
||||
"integrity": "sha512-6Bl6XsDT1ntE0lHbIhr4Kp2PGcleGZ66qu5Jqk8lc0Xc/IeG6gVLmwUGs/K0Us+L8VWoKgj0uWdPMataOsm31w=="
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.10",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
|
||||
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -10764,12 +10769,10 @@
|
|||
}
|
||||
},
|
||||
"redux": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
|
||||
"integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.0.tgz",
|
||||
"integrity": "sha512-NnnHF0h0WVE/hXyrB6OlX67LYRuaf/rJcbWvnHHEPCF/Xa/AZpwhs/20WyqzQae5x4SD2F9nPObgBh2rxAgLiA==",
|
||||
"requires": {
|
||||
"lodash": "4.17.4",
|
||||
"lodash-es": "4.17.5",
|
||||
"loose-envify": "1.3.1",
|
||||
"symbol-observable": "1.2.0"
|
||||
}
|
||||
|
|
|
@ -67,8 +67,8 @@
|
|||
"react-native-sound": "0.10.9",
|
||||
"react-native-vector-icons": "4.4.2",
|
||||
"react-native-webrtc": "github:jitsi/react-native-webrtc#52fe4646401408e0569e972cabf08f3c21b7a107",
|
||||
"react-redux": "5.0.6",
|
||||
"redux": "3.7.2",
|
||||
"react-redux": "5.0.7",
|
||||
"redux": "4.0.0",
|
||||
"redux-thunk": "2.2.0",
|
||||
"strophe.js": "github:jitsi/strophejs#1.2.14-1",
|
||||
"strophejs-plugin-disco": "0.0.2",
|
||||
|
|
|
@ -10,7 +10,11 @@ import Thunk from 'redux-thunk';
|
|||
import { i18next } from '../../base/i18n';
|
||||
import { localParticipantLeft } from '../../base/participants';
|
||||
import { Fragment, RouteRegistry } from '../../base/react';
|
||||
import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
|
||||
import {
|
||||
MiddlewareRegistry,
|
||||
ReducerRegistry,
|
||||
StateListenerRegistry
|
||||
} from '../../base/redux';
|
||||
import { SoundCollection } from '../../base/sounds';
|
||||
import { PersistenceRegistry } from '../../base/storage';
|
||||
import { toURLString } from '../../base/util';
|
||||
|
@ -386,14 +390,13 @@ export class AbstractApp extends Component {
|
|||
* @returns {Store} - The redux store to be used by this
|
||||
* {@code AbstractApp}.
|
||||
*/
|
||||
_maybeCreateStore(props) {
|
||||
_maybeCreateStore({ store }) {
|
||||
// The application Jitsi Meet is architected with redux. However, I do
|
||||
// not want consumers of the App React Component to be forced into
|
||||
// dealing with redux. If the consumer did not provide an external redux
|
||||
// store, utilize an internal redux store.
|
||||
let store = props.store;
|
||||
|
||||
if (typeof store === 'undefined') {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
store = this._createStore();
|
||||
|
||||
// This is temporary workaround to be able to dispatch actions from
|
||||
|
@ -405,6 +408,9 @@ export class AbstractApp extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
// StateListenerRegistry
|
||||
store && StateListenerRegistry.subscribe(store);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
|
|
|
@ -125,13 +125,14 @@ function _addConferenceListeners(conference, dispatch) {
|
|||
conference.on(
|
||||
JitsiConferenceEvents.DISPLAY_NAME_CHANGED,
|
||||
(id, displayName) => dispatch(participantUpdated({
|
||||
conference,
|
||||
id,
|
||||
name: displayName.substr(0, MAX_DISPLAY_NAME_LENGTH)
|
||||
})));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
|
||||
(...args) => dispatch(dominantSpeakerChanged(...args)));
|
||||
id => dispatch(dominantSpeakerChanged(id, conference)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED,
|
||||
|
@ -140,13 +141,14 @@ function _addConferenceListeners(conference, dispatch) {
|
|||
conference.on(
|
||||
JitsiConferenceEvents.USER_JOINED,
|
||||
(id, user) => dispatch(participantJoined({
|
||||
conference,
|
||||
id,
|
||||
name: user.getDisplayName(),
|
||||
role: user.getRole()
|
||||
})));
|
||||
conference.on(
|
||||
JitsiConferenceEvents.USER_LEFT,
|
||||
(...args) => dispatch(participantLeft(...args)));
|
||||
id => dispatch(participantLeft(id, conference)));
|
||||
conference.on(
|
||||
JitsiConferenceEvents.USER_ROLE_CHANGED,
|
||||
(...args) => dispatch(participantRoleChanged(...args)));
|
||||
|
@ -154,18 +156,21 @@ function _addConferenceListeners(conference, dispatch) {
|
|||
conference.addCommandListener(
|
||||
AVATAR_ID_COMMAND,
|
||||
(data, id) => dispatch(participantUpdated({
|
||||
conference,
|
||||
id,
|
||||
avatarID: data.value
|
||||
})));
|
||||
conference.addCommandListener(
|
||||
AVATAR_URL_COMMAND,
|
||||
(data, id) => dispatch(participantUpdated({
|
||||
conference,
|
||||
id,
|
||||
avatarURL: data.value
|
||||
})));
|
||||
conference.addCommandListener(
|
||||
EMAIL_COMMAND,
|
||||
(data, id) => dispatch(participantUpdated({
|
||||
conference,
|
||||
id,
|
||||
email: data.value
|
||||
})));
|
||||
|
|
|
@ -143,7 +143,10 @@ function _overwriteLocalParticipant(
|
|||
|
||||
if ((avatarURL || email || name)
|
||||
&& (localParticipant = getLocalParticipant(getState))) {
|
||||
const newProperties: Object = { id: localParticipant.id };
|
||||
const newProperties: Object = {
|
||||
id: localParticipant.id,
|
||||
local: true
|
||||
};
|
||||
|
||||
if (avatarURL) {
|
||||
newProperties.avatarURL = avatarURL;
|
||||
|
@ -264,7 +267,10 @@ function _undoOverwriteLocalParticipant(
|
|||
|
||||
if ((avatarURL || name || email)
|
||||
&& (localParticipant = getLocalParticipant(getState))) {
|
||||
const newProperties: Object = { id: localParticipant.id };
|
||||
const newProperties: Object = {
|
||||
id: localParticipant.id,
|
||||
local: true
|
||||
};
|
||||
|
||||
if (avatarURL === localParticipant.avatarURL) {
|
||||
newProperties.avatarURL = undefined;
|
||||
|
|
|
@ -22,17 +22,23 @@ import { getLocalParticipant } from './functions';
|
|||
* Create an action for when dominant speaker changes.
|
||||
*
|
||||
* @param {string} id - Participant's ID.
|
||||
* @param {JitsiConference} conference - The {@code JitsiConference} associated
|
||||
* with the participant identified by the specified {@code id}. Only the local
|
||||
* participant is allowed to not specify an associated {@code JitsiConference}
|
||||
* instance.
|
||||
* @returns {{
|
||||
* type: DOMINANT_SPEAKER_CHANGED,
|
||||
* participant: {
|
||||
* conference: JitsiConference,
|
||||
* id: string
|
||||
* }
|
||||
* }}
|
||||
*/
|
||||
export function dominantSpeakerChanged(id) {
|
||||
export function dominantSpeakerChanged(id, conference) {
|
||||
return {
|
||||
type: DOMINANT_SPEAKER_CHANGED,
|
||||
participant: {
|
||||
conference,
|
||||
id
|
||||
}
|
||||
};
|
||||
|
@ -123,7 +129,19 @@ export function localParticipantLeft() {
|
|||
const participant = getLocalParticipant(getState);
|
||||
|
||||
if (participant) {
|
||||
return dispatch(participantLeft(participant.id));
|
||||
return (
|
||||
dispatch(
|
||||
participantLeft(
|
||||
participant.id,
|
||||
|
||||
// XXX Only the local participant is allowed to leave
|
||||
// without stating the JitsiConference instance because
|
||||
// the local participant is uniquely identified by the
|
||||
// very fact that there is only one local participant
|
||||
// (and the fact that the local participant "joins" at
|
||||
// the beginning of the app and "leaves" at the end of
|
||||
// the app).
|
||||
undefined)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -214,11 +232,16 @@ export function participantDisplayNameChanged(id, displayName = '') {
|
|||
*
|
||||
* @param {Participant} participant - Information about participant.
|
||||
* @returns {{
|
||||
* type: PARTICIPANT_JOINED,
|
||||
* participant: Participant
|
||||
* type: PARTICIPANT_JOINED,
|
||||
* participant: Participant
|
||||
* }}
|
||||
*/
|
||||
export function participantJoined(participant) {
|
||||
if (!participant.local && !participant.conference) {
|
||||
throw Error(
|
||||
'A remote participant must be associated with a JitsiConference!');
|
||||
}
|
||||
|
||||
return {
|
||||
type: PARTICIPANT_JOINED,
|
||||
participant
|
||||
|
@ -229,17 +252,23 @@ export function participantJoined(participant) {
|
|||
* Action to signal that a participant has left.
|
||||
*
|
||||
* @param {string} id - Participant's ID.
|
||||
* @param {JitsiConference} conference - The {@code JitsiConference} associated
|
||||
* with the participant identified by the specified {@code id}. Only the local
|
||||
* participant is allowed to not specify an associated {@code JitsiConference}
|
||||
* instance.
|
||||
* @returns {{
|
||||
* type: PARTICIPANT_LEFT,
|
||||
* participant: {
|
||||
* conference: JitsiConference,
|
||||
* id: string
|
||||
* }
|
||||
* }}
|
||||
*/
|
||||
export function participantLeft(id) {
|
||||
export function participantLeft(id, conference) {
|
||||
return {
|
||||
type: PARTICIPANT_LEFT,
|
||||
participant: {
|
||||
conference,
|
||||
id
|
||||
}
|
||||
};
|
||||
|
@ -393,7 +422,5 @@ export function showParticipantJoinedNotification(displayName) {
|
|||
joinedParticipantsNames.push(
|
||||
displayName || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME);
|
||||
|
||||
return dispatch => {
|
||||
_throttledNotifyParticipantConnected(dispatch);
|
||||
};
|
||||
return dispatch => _throttledNotifyParticipantConnected(dispatch);
|
||||
}
|
||||
|
|
|
@ -313,10 +313,7 @@ function _toBoolean(value, undefinedValue) {
|
|||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
const { participantId } = ownProps;
|
||||
const participant
|
||||
= getParticipantById(
|
||||
state['features/base/participants'],
|
||||
participantId);
|
||||
const participant = getParticipantById(state, participantId);
|
||||
let avatar;
|
||||
let connectionStatus;
|
||||
let participantName;
|
||||
|
|
|
@ -127,9 +127,7 @@ export function getLocalParticipant(stateful: Object | Function) {
|
|||
* @private
|
||||
* @returns {(Participant|undefined)}
|
||||
*/
|
||||
export function getParticipantById(
|
||||
stateful: Object | Function,
|
||||
id: string) {
|
||||
export function getParticipantById(stateful: Object | Function, id: string) {
|
||||
const participants = _getAllParticipants(stateful);
|
||||
|
||||
return participants.find(p => p.id === id);
|
||||
|
@ -242,11 +240,8 @@ export function isLocalParticipantModerator(stateful: Object | Function) {
|
|||
return false;
|
||||
}
|
||||
|
||||
const isModerator = localParticipant.role === PARTICIPANT_ROLE.MODERATOR;
|
||||
|
||||
if (state['features/base/config'].enableUserRolesBasedOnToken) {
|
||||
return isModerator && !state['features/base/jwt'].isGuest;
|
||||
}
|
||||
|
||||
return isModerator;
|
||||
return (
|
||||
localParticipant.role === PARTICIPANT_ROLE.MODERATOR
|
||||
&& (!state['features/base/config'].enableUserRolesBasedOnToken
|
||||
|| !state['features/base/jwt'].isGuest));
|
||||
}
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
// @flow
|
||||
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
|
||||
import {
|
||||
CONFERENCE_WILL_JOIN,
|
||||
CONFERENCE_LEFT
|
||||
} from '../conference';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { CONFERENCE_LEFT, CONFERENCE_WILL_JOIN } from '../conference';
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import { playSound, registerSound, unregisterSound } from '../sounds';
|
||||
|
||||
import {
|
||||
localParticipantIdChanged,
|
||||
localParticipantJoined,
|
||||
participantLeft,
|
||||
participantUpdated
|
||||
} from './actions';
|
||||
import {
|
||||
|
@ -43,17 +40,10 @@ declare var APP: Object;
|
|||
* Middleware that captures CONFERENCE_JOINED and CONFERENCE_LEFT actions and
|
||||
* updates respectively ID of local participant.
|
||||
*
|
||||
* @param {Store} store - Redux store.
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const { conference } = store.getState()['features/base/conference'];
|
||||
|
||||
if (action.type === PARTICIPANT_JOINED
|
||||
|| action.type === PARTICIPANT_LEFT) {
|
||||
_maybePlaySounds(store, action);
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT:
|
||||
_registerSounds(store);
|
||||
|
@ -74,32 +64,36 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
|
||||
case DOMINANT_SPEAKER_CHANGED: {
|
||||
// Ensure the raised hand state is cleared for the dominant speaker.
|
||||
|
||||
const { conference, id } = action.participant;
|
||||
const participant = getLocalParticipant(store.getState());
|
||||
|
||||
if (participant) {
|
||||
const local = participant.id === action.participant.id;
|
||||
|
||||
store.dispatch(participantUpdated({
|
||||
id: action.participant.id,
|
||||
local,
|
||||
participant
|
||||
&& store.dispatch(participantUpdated({
|
||||
conference,
|
||||
id,
|
||||
local: participant.id === id,
|
||||
raisedHand: false
|
||||
}));
|
||||
}
|
||||
|
||||
if (typeof APP === 'object') {
|
||||
APP.UI.markDominantSpeaker(action.participant.id);
|
||||
}
|
||||
typeof APP === 'object' && APP.UI.markDominantSpeaker(id);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case KICK_PARTICIPANT:
|
||||
case KICK_PARTICIPANT: {
|
||||
const { conference } = store.getState()['features/base/conference'];
|
||||
|
||||
conference.kickParticipant(action.id);
|
||||
break;
|
||||
}
|
||||
|
||||
case MUTE_REMOTE_PARTICIPANT: {
|
||||
const { conference } = store.getState()['features/base/conference'];
|
||||
|
||||
case MUTE_REMOTE_PARTICIPANT:
|
||||
conference.muteParticipant(action.id);
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO Remove this middleware when the local display name update flow is
|
||||
// fully brought into redux.
|
||||
|
@ -116,73 +110,46 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
}
|
||||
|
||||
case PARTICIPANT_JOINED:
|
||||
case PARTICIPANT_UPDATED: {
|
||||
const { participant } = action;
|
||||
const { id, local, raisedHand } = participant;
|
||||
_maybePlaySounds(store, action);
|
||||
|
||||
// Send an external update of the local participant's raised hand state
|
||||
// if a new raised hand state is defined in the action.
|
||||
if (typeof raisedHand !== 'undefined') {
|
||||
if (local) {
|
||||
conference.setLocalParticipantProperty(
|
||||
'raisedHand',
|
||||
raisedHand);
|
||||
}
|
||||
|
||||
if (typeof APP === 'object') {
|
||||
if (local) {
|
||||
APP.UI.onLocalRaiseHandChanged(raisedHand);
|
||||
APP.UI.setLocalRaisedHandStatus(raisedHand);
|
||||
} else {
|
||||
const remoteParticipant
|
||||
= getParticipantById(store.getState(), id);
|
||||
|
||||
remoteParticipant
|
||||
&& APP.UI.setRaisedHandStatus(
|
||||
remoteParticipant.id,
|
||||
remoteParticipant.name,
|
||||
raisedHand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify external listeners of potential avatarURL changes.
|
||||
if (typeof APP === 'object') {
|
||||
const preUpdateAvatarURL
|
||||
= getAvatarURLByParticipantId(store.getState(), id);
|
||||
|
||||
// Allow the redux update to go through and compare the old avatar
|
||||
// to the new avatar and emit out change events if necessary.
|
||||
const result = next(action);
|
||||
|
||||
const postUpdateAvatarURL
|
||||
= getAvatarURLByParticipantId(store.getState(), id);
|
||||
|
||||
if (preUpdateAvatarURL !== postUpdateAvatarURL) {
|
||||
const currentKnownId = local
|
||||
? APP.conference.getMyUserId() : id;
|
||||
|
||||
APP.UI.refreshAvatarDisplay(
|
||||
currentKnownId, postUpdateAvatarURL);
|
||||
APP.API.notifyAvatarChanged(
|
||||
currentKnownId, postUpdateAvatarURL);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return _participantJoinedOrUpdated(store, next, action);
|
||||
|
||||
case PARTICIPANT_LEFT:
|
||||
_maybePlaySounds(store, action);
|
||||
break;
|
||||
}
|
||||
|
||||
case PARTICIPANT_UPDATED:
|
||||
return _participantJoinedOrUpdated(store, next, action);
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Syncs the redux state features/base/participants up with the redux state
|
||||
* features/base/conference by ensuring that the former does not contain remote
|
||||
* participants no longer relevant to the latter. Introduced to address an issue
|
||||
* with multiplying thumbnails in the filmstrip.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => {
|
||||
const { conference, joining } = state['features/base/conference'];
|
||||
|
||||
return conference || joining;
|
||||
},
|
||||
/* listener */ (conference, { dispatch, getState }) => {
|
||||
for (const p of getState()['features/base/participants']) {
|
||||
!p.local
|
||||
&& (!conference || p.conference !== conference)
|
||||
&& dispatch(participantLeft(p.id, p.conference));
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Initializes the local participant and signals that it joined.
|
||||
*
|
||||
* @private
|
||||
* @param {Store} store - The Redux store.
|
||||
* @param {Store} store - The redux store.
|
||||
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||
* specified action to the specified store.
|
||||
* @param {Action} action - The redux action which is being dispatched
|
||||
|
@ -192,15 +159,15 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
*/
|
||||
function _localParticipantJoined({ getState, dispatch }, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
const settings = getState()['features/base/settings'];
|
||||
const localParticipant = {
|
||||
|
||||
dispatch(localParticipantJoined({
|
||||
avatarID: settings.avatarID,
|
||||
avatarURL: settings.avatarURL,
|
||||
email: settings.email,
|
||||
name: settings.displayName
|
||||
};
|
||||
|
||||
dispatch(localParticipantJoined(localParticipant));
|
||||
}));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -208,8 +175,8 @@ function _localParticipantJoined({ getState, dispatch }, next, action) {
|
|||
/**
|
||||
* Plays sounds when participants join/leave conference.
|
||||
*
|
||||
* @param {Store} store - The Redux store.
|
||||
* @param {Action} action - The Redux action. Should be either
|
||||
* @param {Store} store - The redux store.
|
||||
* @param {Action} action - The redux action. Should be either
|
||||
* {@link PARTICIPANT_JOINED} or {@link PARTICIPANT_LEFT}.
|
||||
* @private
|
||||
* @returns {void}
|
||||
|
@ -223,8 +190,8 @@ function _maybePlaySounds({ getState, dispatch }, action) {
|
|||
// The intention there was to not play user joined notification in big
|
||||
// conferences where 100th person is joining.
|
||||
if (!action.participant.local
|
||||
&& (!startAudioMuted
|
||||
|| getParticipantCount(state) < startAudioMuted)) {
|
||||
&& (!startAudioMuted
|
||||
|| getParticipantCount(state) < startAudioMuted)) {
|
||||
if (action.type === PARTICIPANT_JOINED) {
|
||||
dispatch(playSound(PARTICIPANT_JOINED_SOUND_ID));
|
||||
} else if (action.type === PARTICIPANT_LEFT) {
|
||||
|
@ -233,30 +200,95 @@ function _maybePlaySounds({ getState, dispatch }, action) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the feature base/participants that the action
|
||||
* {@code PARTICIPANT_JOINED} or {@code PARTICIPANT_UPDATED} is being dispatched
|
||||
* within a specific redux store.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
||||
* specified {@code action} in the specified {@code store}.
|
||||
* @param {Action} action - The redux action {@code PARTICIPANT_JOINED} or
|
||||
* {@code PARTICIPANT_UPDATED} which is being dispatched in the specified
|
||||
* {@code store}.
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _participantJoinedOrUpdated({ getState }, next, action) {
|
||||
const { participant: { id, local, raisedHand } } = action;
|
||||
|
||||
// Send an external update of the local participant's raised hand state
|
||||
// if a new raised hand state is defined in the action.
|
||||
if (typeof raisedHand !== 'undefined') {
|
||||
if (local) {
|
||||
const { conference } = getState()['features/base/conference'];
|
||||
|
||||
conference
|
||||
&& conference.setLocalParticipantProperty(
|
||||
'raisedHand',
|
||||
raisedHand);
|
||||
}
|
||||
|
||||
if (typeof APP === 'object') {
|
||||
if (local) {
|
||||
APP.UI.onLocalRaiseHandChanged(raisedHand);
|
||||
APP.UI.setLocalRaisedHandStatus(raisedHand);
|
||||
} else {
|
||||
const remoteParticipant = getParticipantById(getState(), id);
|
||||
|
||||
remoteParticipant
|
||||
&& APP.UI.setRaisedHandStatus(
|
||||
remoteParticipant.id,
|
||||
remoteParticipant.name,
|
||||
raisedHand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify external listeners of potential avatarURL changes.
|
||||
if (typeof APP === 'object') {
|
||||
const oldAvatarURL = getAvatarURLByParticipantId(getState(), id);
|
||||
|
||||
// Allow the redux update to go through and compare the old avatar
|
||||
// to the new avatar and emit out change events if necessary.
|
||||
const result = next(action);
|
||||
const newAvatarURL = getAvatarURLByParticipantId(getState(), id);
|
||||
|
||||
if (oldAvatarURL !== newAvatarURL) {
|
||||
const currentKnownId = local ? APP.conference.getMyUserId() : id;
|
||||
|
||||
APP.UI.refreshAvatarDisplay(currentKnownId, newAvatarURL);
|
||||
APP.API.notifyAvatarChanged(currentKnownId, newAvatarURL);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers sounds related with the participants feature.
|
||||
*
|
||||
* @param {Store} store - The Redux store.
|
||||
* @param {Store} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _registerSounds({ dispatch }) {
|
||||
dispatch(
|
||||
registerSound(PARTICIPANT_JOINED_SOUND_ID, PARTICIPANT_JOINED_FILE));
|
||||
dispatch(
|
||||
registerSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_FILE));
|
||||
dispatch(registerSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_FILE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters sounds related with the participants feature.
|
||||
*
|
||||
* @param {Store} store - The Redux store.
|
||||
* @param {Store} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _unregisterSounds({ dispatch }) {
|
||||
dispatch(
|
||||
unregisterSound(PARTICIPANT_JOINED_SOUND_ID));
|
||||
dispatch(
|
||||
unregisterSound(PARTICIPANT_LEFT_SOUND_ID));
|
||||
dispatch(unregisterSound(PARTICIPANT_JOINED_SOUND_ID));
|
||||
dispatch(unregisterSound(PARTICIPANT_LEFT_SOUND_ID));
|
||||
}
|
||||
|
|
|
@ -32,12 +32,65 @@ import { LOCAL_PARTICIPANT_DEFAULT_ID, PARTICIPANT_ROLE } from './constants';
|
|||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* These properties should not be bulk assigned when updating a particular
|
||||
* @see Participant.
|
||||
* The participant properties which cannot be updated through
|
||||
* {@link PARTICIPANT_UPDATED}. They either identify the participant or can only
|
||||
* be modified through property-dedicated actions.
|
||||
*
|
||||
* @type {string[]}
|
||||
*/
|
||||
const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE
|
||||
= [ 'dominantSpeaker', 'id', 'local', 'pinned' ];
|
||||
const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE = [
|
||||
|
||||
// The following properties identify the participant:
|
||||
'conference',
|
||||
'id',
|
||||
'local',
|
||||
|
||||
// The following properties can only be modified through property-dedicated
|
||||
// actions:
|
||||
'dominantSpeaker',
|
||||
'pinned'
|
||||
];
|
||||
|
||||
/**
|
||||
* Listen for actions which add, remove, or update the set of participants in
|
||||
* the conference.
|
||||
*
|
||||
* @param {Participant[]} state - List of participants to be modified.
|
||||
* @param {Object} action - Action object.
|
||||
* @param {string} action.type - Type of action.
|
||||
* @param {Participant} action.participant - Information about participant to be
|
||||
* added/removed/modified.
|
||||
* @returns {Participant[]}
|
||||
*/
|
||||
ReducerRegistry.register('features/base/participants', (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case DOMINANT_SPEAKER_CHANGED:
|
||||
case PARTICIPANT_ID_CHANGED:
|
||||
case PARTICIPANT_UPDATED:
|
||||
case PIN_PARTICIPANT:
|
||||
return state.map(p => _participant(p, action));
|
||||
|
||||
case PARTICIPANT_JOINED:
|
||||
return [ ...state, _participantJoined(action) ];
|
||||
|
||||
case PARTICIPANT_LEFT: {
|
||||
// XXX A remote participant is uniquely identified by their id in a
|
||||
// specific JitsiConference instance. The local participant is uniquely
|
||||
// identified by the very fact that there is only one local participant
|
||||
// (and the fact that the local participant "joins" at the beginning of
|
||||
// the app and "leaves" at the end of the app).
|
||||
const { conference, id } = action.participant;
|
||||
|
||||
return state.filter(p =>
|
||||
!(
|
||||
p.id === id
|
||||
&& (p.local
|
||||
|| (conference && p.conference === conference))));
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
|
||||
/**
|
||||
* Reducer function for a single participant.
|
||||
|
@ -67,52 +120,6 @@ function _participant(state: Object = {}, action) {
|
|||
}
|
||||
break;
|
||||
|
||||
case PARTICIPANT_JOINED: {
|
||||
const { participant } = action; // eslint-disable-line no-shadow
|
||||
const {
|
||||
avatarURL,
|
||||
connectionStatus,
|
||||
dominantSpeaker,
|
||||
email,
|
||||
isBot,
|
||||
local,
|
||||
name,
|
||||
pinned,
|
||||
role
|
||||
} = participant;
|
||||
let { avatarID, id } = participant;
|
||||
|
||||
// avatarID
|
||||
//
|
||||
// TODO Get the avatarID of the local participant from localStorage.
|
||||
if (!avatarID && local) {
|
||||
avatarID = randomHexString(32);
|
||||
}
|
||||
|
||||
// id
|
||||
//
|
||||
// XXX The situation of not having an ID for a remote participant should
|
||||
// not happen. Maybe we should raise an error in this case or generate a
|
||||
// random ID.
|
||||
if (!id && local) {
|
||||
id = LOCAL_PARTICIPANT_DEFAULT_ID;
|
||||
}
|
||||
|
||||
return {
|
||||
avatarID,
|
||||
avatarURL,
|
||||
connectionStatus,
|
||||
dominantSpeaker: dominantSpeaker || false,
|
||||
email,
|
||||
id,
|
||||
isBot,
|
||||
local: local || false,
|
||||
name,
|
||||
pinned: pinned || false,
|
||||
role: role || PARTICIPANT_ROLE.NONE
|
||||
};
|
||||
}
|
||||
|
||||
case PARTICIPANT_UPDATED: {
|
||||
const { participant } = action; // eslint-disable-line no-shadow
|
||||
let { id } = participant;
|
||||
|
@ -147,31 +154,60 @@ function _participant(state: Object = {}, action) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Listen for actions which add, remove, or update the set of participants in
|
||||
* the conference.
|
||||
* Reduces a specific redux action of type {@link PARTICIPANT_JOINED} in the
|
||||
* feature base/participants.
|
||||
*
|
||||
* @param {Participant[]} state - List of participants to be modified.
|
||||
* @param {Object} action - Action object.
|
||||
* @param {string} action.type - Type of action.
|
||||
* @param {Participant} action.participant - Information about participant to be
|
||||
* added/removed/modified.
|
||||
* @returns {Participant[]}
|
||||
* @param {Action} action - The redux action of type {@code PARTICIPANT_JOINED}
|
||||
* to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new participant derived from the payload of the
|
||||
* specified {@code action} to be added into the redux state of the feature
|
||||
* base/participants after the reduction of the specified
|
||||
* {@code action}.
|
||||
*/
|
||||
ReducerRegistry.register('features/base/participants', (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case DOMINANT_SPEAKER_CHANGED:
|
||||
case PARTICIPANT_ID_CHANGED:
|
||||
case PARTICIPANT_UPDATED:
|
||||
case PIN_PARTICIPANT:
|
||||
return state.map(p => _participant(p, action));
|
||||
function _participantJoined({ participant }) {
|
||||
const {
|
||||
avatarURL,
|
||||
connectionStatus,
|
||||
dominantSpeaker,
|
||||
email,
|
||||
isBot,
|
||||
local,
|
||||
name,
|
||||
pinned,
|
||||
role
|
||||
} = participant;
|
||||
let { avatarID, conference, id } = participant;
|
||||
|
||||
case PARTICIPANT_JOINED:
|
||||
return [ ...state, _participant(undefined, action) ];
|
||||
if (local) {
|
||||
// avatarID
|
||||
//
|
||||
// TODO Get the avatarID of the local participant from localStorage.
|
||||
avatarID || (avatarID = randomHexString(32));
|
||||
|
||||
case PARTICIPANT_LEFT:
|
||||
return state.filter(p => p.id !== action.participant.id);
|
||||
// conference
|
||||
//
|
||||
// XXX The local participant is not identified in association with a
|
||||
// JitsiConference because it is identified by the very fact that it is
|
||||
// the local participant.
|
||||
conference = undefined;
|
||||
|
||||
default:
|
||||
return state;
|
||||
// id
|
||||
id || (id = LOCAL_PARTICIPANT_DEFAULT_ID);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
avatarID,
|
||||
avatarURL,
|
||||
conference,
|
||||
connectionStatus,
|
||||
dominantSpeaker: dominantSpeaker || false,
|
||||
email,
|
||||
id,
|
||||
isBot,
|
||||
local: local || false,
|
||||
name,
|
||||
pinned: pinned || false,
|
||||
role: role || PARTICIPANT_ROLE.NONE
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
// @flow
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* The type listener supported for registration with
|
||||
* {@link StateListenerRegistry} in association with a {@link Selector}.
|
||||
*
|
||||
* @param {any} selection - The value derived from the redux store/state by the
|
||||
* associated {@code Selector}. Immutable!
|
||||
* @param {Store} store - The redux store. Provided in case the {@code Listener}
|
||||
* needs to {@code dispatch} or {@code getState}. The latter is advisable only
|
||||
* if the {@code Listener} is not to respond to changes to that state.
|
||||
* @param {any} prevSelection - The value previously derived from the redux
|
||||
* store/state by the associated {@code Selector}. The {@code Listener} is
|
||||
* invoked only if {@code prevSelection} and {@code selection} are different.
|
||||
* Immutable!
|
||||
*/
|
||||
type Listener = (selection: any, store: Store, prevSelection: any) => void;
|
||||
|
||||
/**
|
||||
* The type selector supported for registration with
|
||||
* {@link StateListenerRegistry} in association with a {@link Listener}.
|
||||
*
|
||||
* @param {Object} state - The redux state from which the {@code Selector} is to
|
||||
* derive data.
|
||||
* @param {any} prevSelection - The value previously derived from the redux
|
||||
* store/state by the {@code Selector}. Provided in case the {@code Selector}
|
||||
* needs to derive the returned value from the specified {@code state} and
|
||||
* {@code prevSelection}. Immutable!
|
||||
* @returns {any} The value derived from the specified {@code state} and/or
|
||||
* {@code prevSelection}. The associated {@code Listener} will only be invoked
|
||||
* if the returned value is other than {@code prevSelection}.
|
||||
*/
|
||||
type Selector = (state: Object, prevSelection: any) => any;
|
||||
|
||||
/**
|
||||
* A type of a {@link Selector}-{@link Listener} association in which the
|
||||
* {@code Listener} listens to changes in the values derived from a redux
|
||||
* store/state by the {@code Selector}.
|
||||
*/
|
||||
type SelectorListener = {
|
||||
|
||||
/**
|
||||
* The {@code Listener} which listens to changes in the values selected by
|
||||
* {@link selector}.
|
||||
*/
|
||||
listener: Listener,
|
||||
|
||||
/**
|
||||
* The {@code Selector} which selects values whose changes are listened to
|
||||
* by {@link listener}.
|
||||
*/
|
||||
selector: Selector
|
||||
};
|
||||
|
||||
/**
|
||||
* A registry listeners which listen to changes in a redux store/state.
|
||||
*/
|
||||
class StateListenerRegistry {
|
||||
/**
|
||||
* The {@link Listener}s registered with this {@code StateListenerRegistry}
|
||||
* to be notified when the values derived by associated {@link Selector}s
|
||||
* from a redux store/state change.
|
||||
*/
|
||||
_selectorListeners: Set<SelectorListener> = new Set();
|
||||
|
||||
_listener: (Store) => void;
|
||||
|
||||
/**
|
||||
* Invoked by a specific redux store any time an action is dispatched, and
|
||||
* some part of the state (tree) may potentially have changed.
|
||||
*
|
||||
* @param {Object} context - The redux store invoking the listener and the
|
||||
* private state of this {@code StateListenerRegistry} associated with the
|
||||
* redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
_listener({ prevSelections, store }: {
|
||||
prevSelections: Map<SelectorListener, any>,
|
||||
store: Store
|
||||
}) {
|
||||
for (const selectorListener of this._selectorListeners) {
|
||||
const prevSelection = prevSelections.get(selectorListener);
|
||||
|
||||
try {
|
||||
const selection
|
||||
= selectorListener.selector(
|
||||
store.getState(),
|
||||
prevSelection);
|
||||
|
||||
if (prevSelection !== selection) {
|
||||
prevSelections.set(selectorListener, selection);
|
||||
selectorListener.listener(selection, store, prevSelection);
|
||||
}
|
||||
} catch (e) {
|
||||
// Don't let one faulty listener prevent other listeners from
|
||||
// being notified about their associated changes.
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a specific listener to be notified when the value derived by a
|
||||
* specific {@code selector} from a redux store/state changes.
|
||||
*
|
||||
* @param {Function} selector - The pure {@code Function} of the redux
|
||||
* store/state (and the previous selection of made by {@code selector})
|
||||
* which selects the value listened to by the specified {@code listener}.
|
||||
* @param {Function} listener - The listener to register with this
|
||||
* {@code StateListenerRegistry} so that it gets invoked when the value
|
||||
* returned by the specified {@code selector} changes.
|
||||
* @returns {void}
|
||||
*/
|
||||
register(selector: Selector, listener: Listener) {
|
||||
this._selectorListeners.add({
|
||||
listener,
|
||||
selector
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to a specific redux store (so that this instance gets notified
|
||||
* any time an action is dispatched, and some part of the state (tree) of
|
||||
* the specified redux store may potentially have changed).
|
||||
*
|
||||
* @param {Store} store - The redux store to which this
|
||||
* {@code StateListenerRegistry} is to {@code subscribe}.
|
||||
* @returns {void}
|
||||
*/
|
||||
subscribe(store: Store) {
|
||||
// XXX If StateListenerRegistry is not utilized by the app to listen to
|
||||
// state changes, do not bother subscribing to the store at all.
|
||||
if (this._selectorListeners.size) {
|
||||
store.subscribe(
|
||||
this._listener.bind(
|
||||
this,
|
||||
{
|
||||
/**
|
||||
* The previous selections of the {@code Selector}s
|
||||
* registered with this {@code StateListenerRegistry}.
|
||||
*
|
||||
* @type Map<any>
|
||||
*/
|
||||
prevSelections: new Map(),
|
||||
|
||||
/**
|
||||
* The redux store.
|
||||
*
|
||||
* @type Store
|
||||
*/
|
||||
store
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new StateListenerRegistry();
|
|
@ -1,3 +1,4 @@
|
|||
export * from './functions';
|
||||
export { default as MiddlewareRegistry } from './MiddlewareRegistry';
|
||||
export { default as ReducerRegistry } from './ReducerRegistry';
|
||||
export { default as StateListenerRegistry } from './StateListenerRegistry';
|
||||
|
|
|
@ -10,6 +10,7 @@ import { appNavigate } from '../../app';
|
|||
import { connect, disconnect } from '../../base/connection';
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import { CalleeInfoContainer } from '../../base/jwt';
|
||||
import { getParticipantCount } from '../../base/participants';
|
||||
import { Container, LoadingIndicator, TintedView } from '../../base/react';
|
||||
import { TestConnectionInfo } from '../../base/testing';
|
||||
import { createDesiredLocalTracks } from '../../base/tracks';
|
||||
|
@ -383,7 +384,6 @@ function _mapStateToProps(state) {
|
|||
const { connecting, connection } = state['features/base/connection'];
|
||||
const { conference, joining, leaving } = state['features/base/conference'];
|
||||
const { reducedUI } = state['features/base/responsive-ui'];
|
||||
const participants = state['features/base/participants'];
|
||||
|
||||
// XXX There is a window of time between the successful establishment of the
|
||||
// XMPP connection and the subsequent commencement of joining the MUC during
|
||||
|
@ -415,7 +415,7 @@ function _mapStateToProps(state) {
|
|||
* @private
|
||||
* @type {number}
|
||||
*/
|
||||
_participantCount: participants.length,
|
||||
_participantCount: getParticipantCount(state),
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the UI is reduced (to
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
// @flow
|
||||
|
||||
import { getPinnedParticipant } from '../base/participants';
|
||||
import {
|
||||
getParticipantCount,
|
||||
getPinnedParticipant
|
||||
} from '../base/participants';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
|
@ -13,8 +16,7 @@ declare var interfaceConfig: Object;
|
|||
* in the filmstrip, then {@code true}; otherwise, {@code false}.
|
||||
*/
|
||||
export function shouldRemoteVideosBeVisible(state: Object) {
|
||||
const participants = state['features/base/participants'];
|
||||
const participantCount = participants.length;
|
||||
const participantCount = getParticipantCount(state);
|
||||
let pinnedParticipant;
|
||||
|
||||
return Boolean(
|
||||
|
@ -26,7 +28,7 @@ export function shouldRemoteVideosBeVisible(state: Object) {
|
|||
|| (participantCount > 1
|
||||
&& (state['features/filmstrip'].hovered
|
||||
|| state['features/toolbox'].visible
|
||||
|| ((pinnedParticipant = getPinnedParticipant(participants))
|
||||
|| ((pinnedParticipant = getPinnedParticipant(state))
|
||||
&& pinnedParticipant.local)))
|
||||
|
||||
|| (typeof interfaceConfig === 'object'
|
||||
|
|
|
@ -236,10 +236,10 @@ function _mapStateToProps(state) {
|
|||
return {
|
||||
_dialIn: state['features/invite'],
|
||||
_disableAutoShow: state['features/base/config'].iAmRecorder,
|
||||
_liveStreamViewURL: currentLiveStreamingSession
|
||||
&& currentLiveStreamingSession.liveStreamViewURL,
|
||||
_participantCount:
|
||||
getParticipantCount(state['features/base/participants']),
|
||||
_liveStreamViewURL:
|
||||
currentLiveStreamingSession
|
||||
&& currentLiveStreamingSession.liveStreamViewURL,
|
||||
_participantCount: getParticipantCount(state),
|
||||
_toolboxVisible: state['features/toolbox'].visible
|
||||
};
|
||||
}
|
||||
|
|
|
@ -118,15 +118,13 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
* @returns {string} - The presence status.
|
||||
*/
|
||||
function _getParticipantPresence(state, id) {
|
||||
if (!id) {
|
||||
return undefined;
|
||||
}
|
||||
const participants = state['features/base/participants'];
|
||||
const participantById = getParticipantById(participants, id);
|
||||
if (id) {
|
||||
const participantById = getParticipantById(state, id);
|
||||
|
||||
if (!participantById) {
|
||||
return undefined;
|
||||
if (participantById) {
|
||||
return participantById.presence;
|
||||
}
|
||||
}
|
||||
|
||||
return participantById.presence;
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* @flow */
|
||||
|
||||
import { APP_WILL_MOUNT } from '../../app';
|
||||
import { CONFERENCE_FAILED, CONFERENCE_LEFT } from '../../base/conference';
|
||||
import {
|
||||
getAvatarURL,
|
||||
getLocalParticipant,
|
||||
|
@ -37,8 +36,15 @@ const _PREFETCH_AVATAR_URLS = false;
|
|||
MiddlewareRegistry.register(({ getState }) => next => action => {
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT:
|
||||
case CONFERENCE_FAILED:
|
||||
case CONFERENCE_LEFT:
|
||||
// XXX CONFERENCE_FAILED/LEFT are no longer used here because they
|
||||
// are tricky to get right as detectors of the moments in time at which
|
||||
// CachedImage is not used. Anyway, if ImageCache is to be cleared from
|
||||
// time to time, SET_LOCATION_URL is a much easier detector of such
|
||||
// opportune times. Fixes at least one 100%-reproducible case of
|
||||
// "TypeError: Cannot read property handlers of undefined." Anyway, in
|
||||
// order to reduce the re-downloading of the same avatars, eventually we
|
||||
// decided to not clear during the runtime of the app (other that at the
|
||||
// beginning that is).
|
||||
ImageCache && ImageCache.get().clear();
|
||||
break;
|
||||
|
||||
|
|
|
@ -99,9 +99,7 @@ class PresenceLabel extends Component {
|
|||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
const participant
|
||||
= getParticipantById(
|
||||
state['features/base/participants'], ownProps.participantID);
|
||||
const participant = getParticipantById(state, ownProps.participantID);
|
||||
|
||||
return {
|
||||
_presence: participant && participant.presence
|
||||
|
|
|
@ -159,10 +159,7 @@ class RemoteControlAuthorizationDialog extends Component<*> {
|
|||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
const { _displayName, participantId } = ownProps;
|
||||
const participant
|
||||
= getParticipantById(
|
||||
state['features/base/participants'],
|
||||
participantId);
|
||||
const participant = getParticipantById(state, participantId);
|
||||
|
||||
return {
|
||||
_displayName: participant ? participant.name : _displayName
|
||||
|
|
|
@ -505,6 +505,12 @@ class Toolbox extends Component<Props> {
|
|||
const { _localParticipantID, _raisedHand } = this.props;
|
||||
|
||||
this.props.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
|
||||
id: _localParticipantID,
|
||||
local: true,
|
||||
raisedHand: !_raisedHand
|
||||
|
|
|
@ -156,11 +156,11 @@ class WelcomePageSideBar extends Component<Props> {
|
|||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
const _localParticipant = getLocalParticipant(state);
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
return {
|
||||
_avatar: getAvatarURL(_localParticipant),
|
||||
_displayName: getParticipantDisplayName(state, _localParticipant.id),
|
||||
_avatar: getAvatarURL(localParticipant),
|
||||
_displayName: getParticipantDisplayName(state, localParticipant.id),
|
||||
_visible: state['features/welcome'].sideBarVisible
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue