ref(video-layout): get pinned ID directly from redux

This commit is contained in:
Leonard Kim 2018-05-18 12:59:07 -07:00 committed by virtuacoplenny
parent 57f7abc6dd
commit 05801711a7
9 changed files with 110 additions and 76 deletions

View File

@ -1,3 +1,5 @@
/* global APP */
/* /*
* Copyright @ 2015 Atlassian Pty Ltd * Copyright @ 2015 Atlassian Pty Ltd
* *
@ -15,6 +17,10 @@
*/ */
const logger = require('jitsi-meet-logger').getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
import {
getPinnedParticipant,
pinParticipant
} from '../react/features/base/participants';
import UIEvents from '../service/UI/UIEvents'; import UIEvents from '../service/UI/UIEvents';
import VideoLayout from './UI/videolayout/VideoLayout'; import VideoLayout from './UI/videolayout/VideoLayout';
@ -444,11 +450,15 @@ class FollowMe {
if (smallVideo) { if (smallVideo) {
this.nextOnStageTimer = 0; this.nextOnStageTimer = 0;
clearTimeout(this.nextOnStageTimout); clearTimeout(this.nextOnStageTimout);
/* eslint-disable no-mixed-operators */
if (pin && !VideoLayout.isPinned(clickId) if (pin) {
|| !pin && VideoLayout.isPinned(clickId)) { APP.store.dispatch(pinParticipant(clickId));
/* eslint-disable no-mixed-operators */ } else {
VideoLayout.handleVideoThumbClicked(clickId); const { id } = getPinnedParticipant(APP.store.getState()) || {};
if (id === clickId) {
APP.store.dispatch(pinParticipant(null));
}
} }
} else { } else {
// If there's no SmallVideo object for the given id, lets wait and // If there's no SmallVideo object for the given id, lets wait and

View File

@ -959,15 +959,6 @@ UI.getLargeVideo = function() {
return VideoLayout.getLargeVideo(); 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" * Shows "Please go to chrome webstore to install the desktop sharing extension"
* 2 button dialog with buttons - cancel and go to web store. * 2 button dialog with buttons - cancel and go to web store.

View File

@ -317,7 +317,7 @@ export default class SharedVideoManager {
name: 'YouTube' name: 'YouTube'
})); }));
VideoLayout.handleVideoThumbClicked(self.url); thumb.videoClick();
// If we are sending the command and we are starting the player // If we are sending the command and we are starting the player
// we need to continuously send the player current time position // we need to continuously send the player current time position

View File

@ -59,7 +59,7 @@ SharedVideoThumb.prototype.createContainer = function(spanId) {
* The thumb click handler. * The thumb click handler.
*/ */
SharedVideoThumb.prototype.videoClick = function() { SharedVideoThumb.prototype.videoClick = function() {
this.VideoLayout.handleVideoThumbClicked(this.url); this._togglePin();
}; };
/** /**

View File

@ -260,7 +260,7 @@ LocalVideo.prototype._onContainerClick = function(event) {
} }
if (!ignoreClick) { if (!ignoreClick) {
this.VideoLayout.handleVideoThumbClicked(this.id); this._togglePin();
} }
}; };

View File

@ -11,7 +11,10 @@ import { i18next } from '../../../react/features/base/i18n';
import { import {
JitsiParticipantConnectionStatus JitsiParticipantConnectionStatus
} from '../../../react/features/base/lib-jitsi-meet'; } from '../../../react/features/base/lib-jitsi-meet';
import {
getPinnedParticipant,
pinParticipant
} from '../../../react/features/base/participants';
import { PresenceLabel } from '../../../react/features/presence-status'; import { PresenceLabel } from '../../../react/features/presence-status';
import { import {
REMOTE_CONTROL_MENU_STATES, REMOTE_CONTROL_MENU_STATES,
@ -234,10 +237,12 @@ RemoteVideo.prototype._requestRemoteControlPermissions = function() {
if (result === true) { if (result === true) {
// the remote control permissions has been granted // the remote control permissions has been granted
// pin the controlled participant // pin the controlled participant
const pinnedId = this.VideoLayout.getPinnedId(); const pinnedParticipant
= getPinnedParticipant(APP.store.getState()) || {};
const pinnedId = pinnedParticipant.id;
if (pinnedId !== this.id) { if (pinnedId !== this.id) {
this.VideoLayout.handleVideoThumbClicked(this.id); APP.store.dispatch(pinParticipant(this.id));
} }
} }
}, error => { }, error => {
@ -648,7 +653,7 @@ RemoteVideo.prototype._onContainerClick = function(event) {
|| classList.contains('popover'); || classList.contains('popover');
if (!ignoreClick) { if (!ignoreClick) {
this.VideoLayout.handleVideoThumbClicked(this.id); this._togglePin();
} }
// On IE we need to populate this handler on video <object> and it does not // On IE we need to populate this handler on video <object> and it does not

View File

@ -12,7 +12,9 @@ import { AudioLevelIndicator }
from '../../../react/features/audio-level-indicator'; from '../../../react/features/audio-level-indicator';
import { import {
Avatar as AvatarDisplay, Avatar as AvatarDisplay,
getAvatarURLByParticipantId getAvatarURLByParticipantId,
getPinnedParticipant,
pinParticipant
} from '../../../react/features/base/participants'; } from '../../../react/features/base/participants';
import { import {
ConnectionIndicator 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 * Removes the React element responsible for showing connection status, dominant
* speaker, and raised hand icons. * speaker, and raised hand icons.

View File

@ -5,7 +5,6 @@ import {
JitsiParticipantConnectionStatus JitsiParticipantConnectionStatus
} from '../../../react/features/base/lib-jitsi-meet'; } from '../../../react/features/base/lib-jitsi-meet';
import { import {
getParticipants,
getPinnedParticipant, getPinnedParticipant,
pinParticipant pinParticipant
} from '../../../react/features/base/participants'; } 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 * Returns the user ID of the remote participant that is current the dominant
* speaker. * speaker.
@ -50,7 +72,7 @@ function onLocalFlipXChanged(val) {
* @returns {string|null} * @returns {string|null}
*/ */
function getCurrentRemoteDominantSpeakerID() { function getCurrentRemoteDominantSpeakerID() {
const dominantSpeaker = getParticipants(APP.store.getState) const dominantSpeaker = getAllParticipants()
.find(participant => participant.dominantSpeaker); .find(participant => participant.dominantSpeaker);
if (dominantSpeaker) { if (dominantSpeaker) {
@ -93,8 +115,6 @@ const VideoLayout = {
// the local video thumb maybe one pixel // the local video thumb maybe one pixel
this.resizeThumbnails(true); this.resizeThumbnails(true);
this.handleVideoThumbClicked = this.handleVideoThumbClicked.bind(this);
this.registerListeners(); 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 * @paramn {string|null} pinnedParticipantID - The participant ID of the
* pinned. Pass in null to unpin without pinning another participant. * participant that is pinned or null if no one is pinned.
* @returns {void} * @returns {void}
*/ */
pinParticipant(id) { onPinChange(pinnedParticipantID) {
APP.store.dispatch(pinParticipant(id)); if (interfaceConfig.filmStripOnly) {
APP.UI.emitEvent(UIEvents.PINNED_ENDPOINT, id, Boolean(id)); return;
},
/**
* 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);
}
} }
// Unpin if currently pinned. getAllThumbnails().forEach(thumbnail =>
if (pinnedId === id) { thumbnail.focus(pinnedParticipantID === thumbnail.getId()));
this.pinParticipant(null);
// Enable the currently set dominant speaker. if (pinnedParticipantID) {
if (getCurrentRemoteDominantSpeakerID()) { this.updateLargeVideo(pinnedParticipantID);
this.updateLargeVideo(getCurrentRemoteDominantSpeakerID());
} else { } else {
// if there is no currentDominantSpeaker, it can also be const currentDominantSpeakerID
= getCurrentRemoteDominantSpeakerID();
if (currentDominantSpeakerID) {
this.updateLargeVideo(currentDominantSpeakerID);
} else {
// if there is no currentDominantSpeakerID, it can also be
// that local participant is the dominant speaker // that local participant is the dominant speaker
// we should act as a participant has left and was on large // we should act as a participant has left and was on large
// and we should choose somebody (electLastVisibleVideo) // and we should choose somebody (electLastVisibleVideo)
this.updateLargeVideo(this.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} * @returns {void}
*/ */
onDominantSpeakerChanged(id) { onDominantSpeakerChanged(id) {
Object.values(remoteVideos).forEach(remoteVideo => getAllThumbnails().forEach(thumbnail =>
remoteVideo.showDominantSpeakerIndicator( thumbnail.showDominantSpeakerIndicator(id === thumbnail.getId()));
id === remoteVideo.getId()));
localVideoThumbnail.showDominantSpeakerIndicator(
APP.conference.isLocalId(id));
if (!remoteVideos[id]) { if (!remoteVideos[id]) {
return; return;
@ -797,7 +789,7 @@ const VideoLayout = {
// Unlock large video // Unlock large video
if (this.getPinnedId() === id) { if (this.getPinnedId() === id) {
logger.info('Focused video owner has left the conference'); logger.info('Focused video owner has left the conference');
this.pinParticipant(null); APP.store.dispatch(pinParticipant(null));
} }
const remoteVideo = remoteVideos[id]; const remoteVideo = remoteVideos[id];

View File

@ -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'; import { MiddlewareRegistry } from '../base/redux';
declare var APP: Object;
/** /**
* Middleware which intercepts actions and updates the legacy component * Middleware which intercepts actions and updates the legacy component
* {@code VideoLayout} as needed. The purpose of this middleware is to redux-ify * {@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 // eslint-disable-next-line no-unused-vars
MiddlewareRegistry.register(store => next => action => { 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); const result = next(action);
switch (action.type) { switch (action.type) {
case DOMINANT_SPEAKER_CHANGED: case DOMINANT_SPEAKER_CHANGED:
VideoLayout.onDominantSpeakerChanged(action.participant.id); VideoLayout.onDominantSpeakerChanged(action.participant.id);
break; 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; return result;