diff --git a/conference.js b/conference.js index eabbaf451..9eae4ccbf 100644 --- a/conference.js +++ b/conference.js @@ -1253,7 +1253,7 @@ export default { this.localVideo = newStream; this._setSharingScreen(newStream); if (newStream) { - APP.UI.addLocalStream(newStream); + APP.UI.addLocalVideoStream(newStream); } this.setVideoMuteStatus(this.isLocalVideoMuted()); }) @@ -1304,9 +1304,6 @@ export default { replaceLocalTrack(this.localAudio, newStream, room)) .then(() => { this.localAudio = newStream; - if (newStream) { - APP.UI.addLocalStream(newStream); - } this.setAudioMuteStatus(this.isLocalAudioMuted()); }) .then(resolve) diff --git a/modules/UI/UI.js b/modules/UI/UI.js index c6904d5a4..1af849351 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -229,22 +229,11 @@ UI.unbindEvents = () => { }; /** - * Show local stream on UI. + * Show local video stream on UI. * @param {JitsiTrack} track stream to show */ -UI.addLocalStream = track => { - switch (track.getType()) { - case 'audio': - // Local audio is not rendered so no further action is needed at this - // point. - break; - case 'video': - VideoLayout.changeLocalVideo(track); - break; - default: - logger.error(`Unknown stream type: ${track.getType()}`); - break; - } +UI.addLocalVideoStream = track => { + VideoLayout.changeLocalVideo(track); }; /** diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js index 8960611cc..b13375515 100644 --- a/modules/UI/videolayout/VideoLayout.js +++ b/modules/UI/videolayout/VideoLayout.js @@ -53,16 +53,6 @@ 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. * @@ -86,43 +76,6 @@ function getLocalParticipant() { return getLocalParticipantFromStore(APP.store.getState()); } -/** - * Returns the user ID of the remote participant that is current the dominant - * speaker. - * - * @private - * @returns {string|null} - */ -function getCurrentRemoteDominantSpeakerID() { - const dominantSpeaker = getAllParticipants() - .find(participant => participant.dominantSpeaker); - - if (dominantSpeaker) { - return dominantSpeaker.local ? null : dominantSpeaker.id; - } - - return null; -} - -/** - * Returns the corresponding resource id to the given peer container - * DOM element. - * - * @return the corresponding resource id to the given peer container - * DOM element - */ -function getPeerContainerResourceId(containerElement) { - if (localVideoThumbnail.container === containerElement) { - return localVideoThumbnail.id; - } - - const i = containerElement.id.indexOf('participant_'); - - if (i >= 0) { - return containerElement.id.substring(i + 12); - } -} - const VideoLayout = { init(emitter) { eventEmitter = emitter; @@ -208,10 +161,6 @@ const VideoLayout = { * and setting them assume the id is already set. */ mucJoined() { - if (largeVideo && !largeVideo.id) { - this.updateLargeVideo(getLocalParticipant().id, true); - } - // FIXME: replace this call with a generic update call once SmallVideo // only contains a ReactElement. Then remove this call once the // Filmstrip is fully in React. @@ -247,79 +196,6 @@ const VideoLayout = { localVideoThumbnail.setVisible(visible); }, - /** - * Checks if removed video is currently displayed and tries to display - * another one instead. - * Uses focusedID if any or dominantSpeakerID if any, - * otherwise elects new video, in this order. - */ - _updateAfterThumbRemoved(id) { - // Always trigger an update if large video is empty. - if (!largeVideo - || (this.getLargeVideoID() && !this.isCurrentlyOnLarge(id))) { - return; - } - - const pinnedId = this.getPinnedId(); - let newId; - - if (pinnedId) { - newId = pinnedId; - } else if (getCurrentRemoteDominantSpeakerID()) { - newId = getCurrentRemoteDominantSpeakerID(); - } else { // Otherwise select last visible video - newId = this.electLastVisibleVideo(); - } - - this.updateLargeVideo(newId); - }, - - electLastVisibleVideo() { - // pick the last visible video in the row - // if nobody else is left, this picks the local video - const remoteThumbs = Filmstrip.getThumbs(true).remoteThumbs; - let thumbs = remoteThumbs.filter('[id!="mixedstream"]'); - - const lastVisible = thumbs.filter(':visible:last'); - - if (lastVisible.length) { - const id = getPeerContainerResourceId(lastVisible[0]); - - if (remoteVideos[id]) { - logger.info(`electLastVisibleVideo: ${id}`); - - return id; - } - - // The RemoteVideo was removed (but the DOM elements may still - // exist). - } - - logger.info('Last visible video no longer exists'); - thumbs = Filmstrip.getThumbs().remoteThumbs; - if (thumbs.length) { - const id = getPeerContainerResourceId(thumbs[0]); - - if (remoteVideos[id]) { - logger.info(`electLastVisibleVideo: ${id}`); - - return id; - } - - // The RemoteVideo was removed (but the DOM elements may - // still exist). - } - - // Go with local video - logger.info('Fallback to local video...'); - - const { id } = getLocalParticipant(); - - logger.info(`electLastVisibleVideo: ${id}`); - - return id; - }, - onRemoteStreamAdded(stream) { const id = stream.getParticipantId(); const remoteVideo = remoteVideos[id]; @@ -423,23 +299,6 @@ const VideoLayout = { getAllThumbnails().forEach(thumbnail => thumbnail.focus(pinnedParticipantID === thumbnail.getId())); - - if (pinnedParticipantID) { - this.updateLargeVideo(pinnedParticipantID); - } else { - 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 - // we should act as a participant has left and was on large - // and we should choose somebody (electLastVisibleVideo) - this.updateLargeVideo(this.electLastVisibleVideo()); - } - } }, /** @@ -473,19 +332,6 @@ const VideoLayout = { this.updateMutedForNoTracks(id, 'audio'); this.updateMutedForNoTracks(id, 'video'); - - const remoteVideosCount = Object.keys(remoteVideos).length; - - if (remoteVideosCount === 1) { - window.setTimeout(() => { - const updatedRemoteVideosCount - = Object.keys(remoteVideos).length; - - if (updatedRemoteVideosCount === 1 && remoteVideos[id]) { - this._maybePlaceParticipantOnLargeVideo(id); - } - }, 3000); - } }, /** @@ -512,43 +358,14 @@ const VideoLayout = { // FIXME: what does this do??? remoteVideoActive(videoElement, resourceJid) { - logger.info(`${resourceJid} video is now active`, videoElement); - VideoLayout.resizeThumbnails( false, () => { if (videoElement) { $(videoElement).show(); } }); - - this._maybePlaceParticipantOnLargeVideo(resourceJid); - }, - - /** - * Update the large video to the last added video only if there's no current - * dominant, focused speaker or update it to the current dominant speaker. - * - * @params {string} resourceJid - The id of the user to maybe display on - * large video. - * @returns {void} - */ - _maybePlaceParticipantOnLargeVideo(resourceJid) { - const pinnedId = this.getPinnedId(); - - if ((!pinnedId - && !getCurrentRemoteDominantSpeakerID() - && this.isLargeContainerTypeVisible(VIDEO_CONTAINER_TYPE)) - || pinnedId === resourceJid - || (!pinnedId && resourceJid - && getCurrentRemoteDominantSpeakerID() === resourceJid) - - /* Playback started while we're on the stage - may need to update - video source with the new stream */ - || this.isCurrentlyOnLarge(resourceJid)) { - - this.updateLargeVideo(resourceJid, true); - } + this._updateLargeVideoIfDisplayed(resourceJid, true); }, /** @@ -646,10 +463,8 @@ const VideoLayout = { } } - if (this.isCurrentlyOnLarge(id)) { - // large video will show avatar instead of muted stream - this.updateLargeVideo(id, true); - } + // large video will show avatar instead of muted stream + this._updateLargeVideoIfDisplayed(id, true); }, /** @@ -677,18 +492,6 @@ const VideoLayout = { onDominantSpeakerChanged(id) { getAllThumbnails().forEach(thumbnail => thumbnail.showDominantSpeakerIndicator(id === thumbnail.getId())); - - - if (!remoteVideos[id]) { - return; - } - - // Local video will not have container found, but that's ok - // since we don't want to switch to local video. - if (!interfaceConfig.filmStripOnly && !this.getPinnedId() - && !this.getCurrentlyOnLargeContainer().stayOnStage()) { - this.updateLargeVideo(id); - } }, /** @@ -758,9 +561,7 @@ const VideoLayout = { if (remoteVideo) { remoteVideo.updateView(); - if (remoteVideo.isCurrentlyOnLargeVideo()) { - this.updateLargeVideo(id); - } + this._updateLargeVideoIfDisplayed(id); } }, @@ -809,7 +610,6 @@ const VideoLayout = { } VideoLayout.resizeThumbnails(); - VideoLayout._updateAfterThumbRemoved(id); }, onVideoTypeChanged(id, newVideoType) { @@ -835,9 +635,7 @@ const VideoLayout = { } smallVideo.setVideoType(newVideoType); - if (this.isCurrentlyOnLarge(id)) { - this.updateLargeVideo(id, true); - } + this._updateLargeVideoIfDisplayed(id, true); }, /** diff --git a/react/features/base/tracks/middleware.js b/react/features/base/tracks/middleware.js index 78a9136f5..ad2b27be9 100644 --- a/react/features/base/tracks/middleware.js +++ b/react/features/base/tracks/middleware.js @@ -124,9 +124,7 @@ MiddlewareRegistry.register(store => next => action => { } else { APP.UI.setVideoMuted(participantID, muted); } - APP.UI.onPeerVideoTypeChanged( - participantID, - jitsiTrack.videoType); + APP.UI.onPeerVideoTypeChanged(participantID, jitsiTrack.videoType); } else if (jitsiTrack.isLocal()) { APP.conference.setAudioMuteStatus(muted); } else { diff --git a/react/features/large-video/index.js b/react/features/large-video/index.js index 7f0ef0251..12269a10f 100644 --- a/react/features/large-video/index.js +++ b/react/features/large-video/index.js @@ -3,3 +3,4 @@ export * from './components'; import './middleware'; import './reducer'; +import './subscriber'; diff --git a/react/features/large-video/middleware.js b/react/features/large-video/middleware.js index 96bfb2491..7846750b1 100644 --- a/react/features/large-video/middleware.js +++ b/react/features/large-video/middleware.js @@ -38,7 +38,7 @@ MiddlewareRegistry.register(store => next => action => { break; } - + case CONFERENCE_JOINED: case PARTICIPANT_JOINED: case PARTICIPANT_LEFT: case PIN_PARTICIPANT: @@ -47,13 +47,6 @@ MiddlewareRegistry.register(store => next => action => { store.dispatch(selectParticipantInLargeVideo()); break; - case CONFERENCE_JOINED: - // Ensure a participant is selected on conference join. This addresses - // the case where video tracks were received before CONFERENCE_JOINED - // fired; without the conference selection may not happen. - store.dispatch(selectParticipant()); - break; - case TRACK_UPDATED: // In order to minimize re-calculations, we need to select participant // only if the videoType of the current participant rendered in diff --git a/react/features/large-video/subscriber.js b/react/features/large-video/subscriber.js new file mode 100644 index 000000000..9134010e0 --- /dev/null +++ b/react/features/large-video/subscriber.js @@ -0,0 +1,14 @@ +// @flow + +import { StateListenerRegistry } from '../base/redux'; +import VideoLayout from '../../../modules/UI/videolayout/VideoLayout'; + +/** + * Updates the on stage participant video. + */ +StateListenerRegistry.register( + /* selector */ state => state['features/large-video'].participantId, + /* listener */ participantId => { + VideoLayout.updateLargeVideo(participantId, true); + } +);