forked from jitsi/jitsi-meet
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ref(follow-me): hook into redux (jitsi#3991)
Use subscribers to detect state change and emit those out to other participants. Use middleware to register the command listener.
- Loading branch information
1 parent
aefa836
commit c7013f5
Showing
13 changed files
with
332 additions
and
614 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/** | ||
* The (name of the) command which transports the state (represented by | ||
* {State} for the local state at the time of this writing) of a {FollowMe} | ||
* (instance) between participants. | ||
*/ | ||
export const FOLLOW_ME_COMMAND = 'follow-me'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './middleware'; | ||
export * from './subscriber'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
// @flow | ||
|
||
import { CONFERENCE_WILL_JOIN } from '../base/conference'; | ||
import { | ||
getParticipantById, | ||
getPinnedParticipant, | ||
pinParticipant | ||
} from '../base/participants'; | ||
import { MiddlewareRegistry } from '../base/redux'; | ||
import { setFilmstripVisible } from '../filmstrip'; | ||
import { setTileView } from '../video-layout'; | ||
|
||
import { FOLLOW_ME_COMMAND } from './constants'; | ||
|
||
const logger = require('jitsi-meet-logger').getLogger(__filename); | ||
|
||
declare var APP: Object; | ||
|
||
/** | ||
* The timeout after which a follow-me command that has been received will be | ||
* ignored if not consumed. | ||
* | ||
* @type {number} in seconds | ||
* @private | ||
*/ | ||
const _FOLLOW_ME_RECEIVED_TIMEOUT = 30; | ||
|
||
/** | ||
* An instance of a timeout used as a workaround when attempting to pin a | ||
* non-existent particapant, which may be caused by participant join information | ||
* not being received yet. | ||
* | ||
* @type {TimeoutID} | ||
*/ | ||
let nextOnStageTimeout; | ||
|
||
/** | ||
* A count of how many seconds the nextOnStageTimeout has ticked while waiting | ||
* for a participant to be discovered that should be pinned. This variable | ||
* works in conjunction with {@code _FOLLOW_ME_RECEIVED_TIMEOUT} and | ||
* {@code nextOnStageTimeout}. | ||
* | ||
* @type {number} | ||
*/ | ||
let nextOnStageTimer = 0; | ||
|
||
/** | ||
* Represents "Follow Me" feature which enables a moderator to (partially) | ||
* control the user experience/interface (e.g. filmstrip visibility) of (other) | ||
* non-moderator participant. | ||
*/ | ||
MiddlewareRegistry.register(store => next => action => { | ||
switch (action.type) { | ||
case CONFERENCE_WILL_JOIN: { | ||
const { conference } = action; | ||
|
||
conference.addCommandListener( | ||
FOLLOW_ME_COMMAND, ({ attributes }, id) => { | ||
_onFollowMeCommand(attributes, id, store); | ||
}); | ||
} | ||
} | ||
|
||
return next(action); | ||
}); | ||
|
||
/** | ||
* Notifies this instance about a "Follow Me" command received by the Jitsi | ||
* conference. | ||
* | ||
* @param {Object} attributes - The attributes carried by the command. | ||
* @param {string} id - The identifier of the participant who issuing the | ||
* command. A notable idiosyncrasy to be mindful of here is that the command | ||
* may be issued by the local participant. | ||
* @param {Object} store - The redux store. Used to calculate and dispatch | ||
* updates. | ||
* @private | ||
* @returns {void} | ||
*/ | ||
function _onFollowMeCommand(attributes = {}, id, store) { | ||
const state = store.getState(); | ||
|
||
// We require to know who issued the command because (1) only a | ||
// moderator is allowed to send commands and (2) a command MUST be | ||
// issued by a defined commander. | ||
if (typeof id === 'undefined') { | ||
return; | ||
} | ||
|
||
const participantSendingCommand = getParticipantById(state, id); | ||
|
||
// The Command(s) API will send us our own commands and we don't want | ||
// to act upon them. | ||
if (participantSendingCommand.local) { | ||
return; | ||
} | ||
|
||
if (participantSendingCommand.role !== 'moderator') { | ||
logger.warn('Received follow-me command not from moderator'); | ||
|
||
return; | ||
} | ||
|
||
// XMPP will translate all booleans to strings, so explicitly check against | ||
// the string form of the boolean {@code true}. | ||
store.dispatch(setFilmstripVisible(attributes.filmstripVisible === 'true')); | ||
store.dispatch(setTileView(attributes.tileViewEnabled === 'true')); | ||
|
||
// For now gate etherpad checks behind a web-app check to be extra safe | ||
// against calling a web-app global. | ||
if (typeof APP !== 'undefined' && state['features/etherpad'].initialized) { | ||
const isEtherpadVisible = attributes.sharedDocumentVisible === 'true'; | ||
const documentManager = APP.UI.getSharedDocumentManager(); | ||
|
||
if (documentManager | ||
&& isEtherpadVisible !== state['features/etherpad'].editing) { | ||
documentManager.toggleEtherpad(); | ||
} | ||
} | ||
|
||
const pinnedParticipant | ||
= getPinnedParticipant(state, attributes.nextOnStage); | ||
const idOfParticipantToPin = attributes.nextOnStage; | ||
|
||
if (typeof idOfParticipantToPin !== 'undefined' | ||
&& (!pinnedParticipant | ||
|| idOfParticipantToPin !== pinnedParticipant.id)) { | ||
_pinVideoThumbnailById(store, idOfParticipantToPin); | ||
} else if (typeof idOfParticipantToPin === 'undefined' | ||
&& pinnedParticipant) { | ||
store.dispatch(pinParticipant(null)); | ||
} | ||
} | ||
|
||
/** | ||
* Pins the video thumbnail given by clickId. | ||
* | ||
* @param {Object} store - The redux store. | ||
* @param {string} clickId - The identifier of the participant to pin. | ||
* @private | ||
* @returns {void} | ||
*/ | ||
function _pinVideoThumbnailById(store, clickId) { | ||
if (getParticipantById(store.getState(), clickId)) { | ||
clearTimeout(nextOnStageTimeout); | ||
nextOnStageTimer = 0; | ||
|
||
store.dispatch(pinParticipant(clickId)); | ||
} else { | ||
nextOnStageTimeout = setTimeout(() => { | ||
if (nextOnStageTimer > _FOLLOW_ME_RECEIVED_TIMEOUT) { | ||
nextOnStageTimer = 0; | ||
|
||
return; | ||
} | ||
|
||
nextOnStageTimer++; | ||
|
||
_pinVideoThumbnailById(store, clickId); | ||
}, 1000); | ||
} | ||
} |
Oops, something went wrong.