From 05801711a7182d03216311a7dff264ad235dbbad Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Fri, 18 May 2018 12:59:07 -0700 Subject: [PATCH] ref(video-layout): get pinned ID directly from redux --- modules/FollowMe.js | 20 +++- modules/UI/UI.js | 9 -- modules/UI/shared_video/SharedVideo.js | 2 +- modules/UI/shared_video/SharedVideoThumb.js | 2 +- modules/UI/videolayout/LocalVideo.js | 2 +- modules/UI/videolayout/RemoteVideo.js | 13 ++- modules/UI/videolayout/SmallVideo.js | 20 +++- modules/UI/videolayout/VideoLayout.js | 96 +++++++++---------- react/features/video-layout/middleware.web.js | 22 ++++- 9 files changed, 110 insertions(+), 76 deletions(-) diff --git a/modules/FollowMe.js b/modules/FollowMe.js index be9ac89cb..239e1f68d 100644 --- a/modules/FollowMe.js +++ b/modules/FollowMe.js @@ -1,3 +1,5 @@ +/* global APP */ + /* * Copyright @ 2015 Atlassian Pty Ltd * @@ -15,6 +17,10 @@ */ const logger = require('jitsi-meet-logger').getLogger(__filename); +import { + getPinnedParticipant, + pinParticipant +} from '../react/features/base/participants'; import UIEvents from '../service/UI/UIEvents'; import VideoLayout from './UI/videolayout/VideoLayout'; @@ -444,11 +450,15 @@ class FollowMe { if (smallVideo) { this.nextOnStageTimer = 0; clearTimeout(this.nextOnStageTimout); - /* eslint-disable no-mixed-operators */ - if (pin && !VideoLayout.isPinned(clickId) - || !pin && VideoLayout.isPinned(clickId)) { - /* eslint-disable no-mixed-operators */ - VideoLayout.handleVideoThumbClicked(clickId); + + if (pin) { + APP.store.dispatch(pinParticipant(clickId)); + } else { + const { id } = getPinnedParticipant(APP.store.getState()) || {}; + + if (id === clickId) { + APP.store.dispatch(pinParticipant(null)); + } } } else { // If there's no SmallVideo object for the given id, lets wait and diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 8dd4a88da..dcb4afdb4 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -959,15 +959,6 @@ UI.getLargeVideo = function() { return VideoLayout.getLargeVideo(); }; -/** - * Returns whether or not the passed in user id is currently pinned to the large - * video. - * - * @param {string} userId - The id of the user to check is pinned or not. - * @returns {boolean} True if the user is currently pinned to the large video. - */ -UI.isPinned = userId => VideoLayout.getPinnedId() === userId; - /** * Shows "Please go to chrome webstore to install the desktop sharing extension" * 2 button dialog with buttons - cancel and go to web store. diff --git a/modules/UI/shared_video/SharedVideo.js b/modules/UI/shared_video/SharedVideo.js index 494d43a92..459be100f 100644 --- a/modules/UI/shared_video/SharedVideo.js +++ b/modules/UI/shared_video/SharedVideo.js @@ -317,7 +317,7 @@ export default class SharedVideoManager { name: 'YouTube' })); - VideoLayout.handleVideoThumbClicked(self.url); + thumb.videoClick(); // If we are sending the command and we are starting the player // we need to continuously send the player current time position diff --git a/modules/UI/shared_video/SharedVideoThumb.js b/modules/UI/shared_video/SharedVideoThumb.js index a33f73fc4..340732948 100644 --- a/modules/UI/shared_video/SharedVideoThumb.js +++ b/modules/UI/shared_video/SharedVideoThumb.js @@ -59,7 +59,7 @@ SharedVideoThumb.prototype.createContainer = function(spanId) { * The thumb click handler. */ SharedVideoThumb.prototype.videoClick = function() { - this.VideoLayout.handleVideoThumbClicked(this.url); + this._togglePin(); }; /** diff --git a/modules/UI/videolayout/LocalVideo.js b/modules/UI/videolayout/LocalVideo.js index f9ba8e593..1835de0bd 100644 --- a/modules/UI/videolayout/LocalVideo.js +++ b/modules/UI/videolayout/LocalVideo.js @@ -260,7 +260,7 @@ LocalVideo.prototype._onContainerClick = function(event) { } if (!ignoreClick) { - this.VideoLayout.handleVideoThumbClicked(this.id); + this._togglePin(); } }; diff --git a/modules/UI/videolayout/RemoteVideo.js b/modules/UI/videolayout/RemoteVideo.js index 150818f46..a62464ab7 100644 --- a/modules/UI/videolayout/RemoteVideo.js +++ b/modules/UI/videolayout/RemoteVideo.js @@ -11,7 +11,10 @@ import { i18next } from '../../../react/features/base/i18n'; import { JitsiParticipantConnectionStatus } from '../../../react/features/base/lib-jitsi-meet'; - +import { + getPinnedParticipant, + pinParticipant +} from '../../../react/features/base/participants'; import { PresenceLabel } from '../../../react/features/presence-status'; import { REMOTE_CONTROL_MENU_STATES, @@ -234,10 +237,12 @@ RemoteVideo.prototype._requestRemoteControlPermissions = function() { if (result === true) { // the remote control permissions has been granted // pin the controlled participant - const pinnedId = this.VideoLayout.getPinnedId(); + const pinnedParticipant + = getPinnedParticipant(APP.store.getState()) || {}; + const pinnedId = pinnedParticipant.id; if (pinnedId !== this.id) { - this.VideoLayout.handleVideoThumbClicked(this.id); + APP.store.dispatch(pinParticipant(this.id)); } } }, error => { @@ -648,7 +653,7 @@ RemoteVideo.prototype._onContainerClick = function(event) { || classList.contains('popover'); if (!ignoreClick) { - this.VideoLayout.handleVideoThumbClicked(this.id); + this._togglePin(); } // On IE we need to populate this handler on video and it does not diff --git a/modules/UI/videolayout/SmallVideo.js b/modules/UI/videolayout/SmallVideo.js index cff14a439..537accf86 100644 --- a/modules/UI/videolayout/SmallVideo.js +++ b/modules/UI/videolayout/SmallVideo.js @@ -12,7 +12,9 @@ import { AudioLevelIndicator } from '../../../react/features/audio-level-indicator'; import { Avatar as AvatarDisplay, - getAvatarURLByParticipantId + getAvatarURLByParticipantId, + getPinnedParticipant, + pinParticipant } from '../../../react/features/base/participants'; import { ConnectionIndicator @@ -819,6 +821,22 @@ SmallVideo.prototype.updateIndicators = function() { ); }; +/** + * Pins the participant displayed by this thumbnail or unpins if already pinned. + * + * @private + * @returns {void} + */ +SmallVideo.prototype._togglePin = function() { + const pinnedParticipant + = getPinnedParticipant(APP.store.getState()) || {}; + const participantIdToPin + = pinnedParticipant && pinnedParticipant.id === this.id + ? null : this.id; + + APP.store.dispatch(pinParticipant(participantIdToPin)); +}; + /** * Removes the React element responsible for showing connection status, dominant * speaker, and raised hand icons. diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js index 11f4e7521..1f9cd2529 100644 --- a/modules/UI/videolayout/VideoLayout.js +++ b/modules/UI/videolayout/VideoLayout.js @@ -5,7 +5,6 @@ import { JitsiParticipantConnectionStatus } from '../../../react/features/base/lib-jitsi-meet'; import { - getParticipants, getPinnedParticipant, pinParticipant } from '../../../react/features/base/participants'; @@ -42,6 +41,29 @@ function onLocalFlipXChanged(val) { } } +/** + * Returns the redux representation of all known users. + * + * @private + * @returns {Array} + */ +function getAllParticipants() { + return APP.store.getState()['features/base/participants']; +} + +/** + * Returns an array of all thumbnails in the filmstrip. + * + * @private + * @returns {Array} + */ +function getAllThumbnails() { + return [ + localVideoThumbnail, + ...Object.values(remoteVideos) + ]; +} + /** * Returns the user ID of the remote participant that is current the dominant * speaker. @@ -50,7 +72,7 @@ function onLocalFlipXChanged(val) { * @returns {string|null} */ function getCurrentRemoteDominantSpeakerID() { - const dominantSpeaker = getParticipants(APP.store.getState) + const dominantSpeaker = getAllParticipants() .find(participant => participant.dominantSpeaker); if (dominantSpeaker) { @@ -93,8 +115,6 @@ const VideoLayout = { // the local video thumb maybe one pixel this.resizeThumbnails(true); - this.handleVideoThumbClicked = this.handleVideoThumbClicked.bind(this); - this.registerListeners(); }, @@ -376,61 +396,36 @@ const VideoLayout = { }, /** - * Updates the desired pinned participant and notifies web UI of the change. + * Callback invoked to update display when the pin participant has changed. * - * @param {string|null} id - The participant id of the participant to be - * pinned. Pass in null to unpin without pinning another participant. + * @paramn {string|null} pinnedParticipantID - The participant ID of the + * participant that is pinned or null if no one is pinned. * @returns {void} */ - pinParticipant(id) { - APP.store.dispatch(pinParticipant(id)); - APP.UI.emitEvent(UIEvents.PINNED_ENDPOINT, id, Boolean(id)); - }, - - /** - * Handles the click on a video thumbnail. - * - * @param id the identifier of the video thumbnail - */ - handleVideoThumbClicked(id) { - const smallVideo = VideoLayout.getSmallVideo(id); - const pinnedId = this.getPinnedId(); - - if (pinnedId) { - const oldSmallVideo = VideoLayout.getSmallVideo(pinnedId); - - if (oldSmallVideo && !interfaceConfig.filmStripOnly) { - oldSmallVideo.focus(false); - } + onPinChange(pinnedParticipantID) { + if (interfaceConfig.filmStripOnly) { + return; } - // Unpin if currently pinned. - if (pinnedId === id) { - this.pinParticipant(null); + getAllThumbnails().forEach(thumbnail => + thumbnail.focus(pinnedParticipantID === thumbnail.getId())); - // Enable the currently set dominant speaker. - if (getCurrentRemoteDominantSpeakerID()) { - this.updateLargeVideo(getCurrentRemoteDominantSpeakerID()); + if (pinnedParticipantID) { + this.updateLargeVideo(pinnedParticipantID); + } else { + const currentDominantSpeakerID + = getCurrentRemoteDominantSpeakerID(); + + if (currentDominantSpeakerID) { + this.updateLargeVideo(currentDominantSpeakerID); } else { - // if there is no currentDominantSpeaker, it can also be + // if there is no currentDominantSpeakerID, it can also be // that local participant is the dominant speaker // we should act as a participant has left and was on large // and we should choose somebody (electLastVisibleVideo) this.updateLargeVideo(this.electLastVisibleVideo()); } - - return; } - - // Update focused/pinned interface. - if (id) { - if (smallVideo && !interfaceConfig.filmStripOnly) { - smallVideo.focus(true); - this.pinParticipant(id); - } - } - - this.updateLargeVideo(id); }, /** @@ -684,12 +679,9 @@ const VideoLayout = { * @returns {void} */ onDominantSpeakerChanged(id) { - Object.values(remoteVideos).forEach(remoteVideo => - remoteVideo.showDominantSpeakerIndicator( - id === remoteVideo.getId())); + getAllThumbnails().forEach(thumbnail => + thumbnail.showDominantSpeakerIndicator(id === thumbnail.getId())); - localVideoThumbnail.showDominantSpeakerIndicator( - APP.conference.isLocalId(id)); if (!remoteVideos[id]) { return; @@ -797,7 +789,7 @@ const VideoLayout = { // Unlock large video if (this.getPinnedId() === id) { logger.info('Focused video owner has left the conference'); - this.pinParticipant(null); + APP.store.dispatch(pinParticipant(null)); } const remoteVideo = remoteVideos[id]; diff --git a/react/features/video-layout/middleware.web.js b/react/features/video-layout/middleware.web.js index 753ecc0d3..758009e3d 100644 --- a/react/features/video-layout/middleware.web.js +++ b/react/features/video-layout/middleware.web.js @@ -1,8 +1,16 @@ -import VideoLayout from '../../../modules/UI/videolayout/VideoLayout.js'; +// @flow -import { DOMINANT_SPEAKER_CHANGED } from '../base/participants'; +import VideoLayout from '../../../modules/UI/videolayout/VideoLayout.js'; +import UIEvents from '../../../service/UI/UIEvents'; + +import { + DOMINANT_SPEAKER_CHANGED, + PIN_PARTICIPANT +} from '../base/participants'; import { MiddlewareRegistry } from '../base/redux'; +declare var APP: Object; + /** * Middleware which intercepts actions and updates the legacy component * {@code VideoLayout} as needed. The purpose of this middleware is to redux-ify @@ -13,12 +21,22 @@ import { MiddlewareRegistry } from '../base/redux'; */ // eslint-disable-next-line no-unused-vars MiddlewareRegistry.register(store => next => action => { + // Purposefully perform additional actions after state update to mimic + // being connected to the store for updates. const result = next(action); switch (action.type) { case DOMINANT_SPEAKER_CHANGED: VideoLayout.onDominantSpeakerChanged(action.participant.id); break; + + case PIN_PARTICIPANT: + VideoLayout.onPinChange(action.participant.id); + APP.UI.emitEvent( + UIEvents.PINNED_ENDPOINT, + action.participant.id, + Boolean(action.participant.id)); + break; } return result;