ref(hangup): clean up some UI state on hangup

- Reset some state on the singletons conference
  and VideoLayout.
- Add a way for LocalVideo to clean itself up
  by sharing logic with the other SmallVideos.
- Add clearing of chat messages so they don't
  linger.
- Remove some UI event listeners.
This commit is contained in:
Leonard Kim 2019-01-01 13:19:34 -08:00
parent 9215b1e8b2
commit 14cc4ea54a
13 changed files with 151 additions and 78 deletions

View File

@ -88,6 +88,7 @@ import {
import { updateSettings } from './react/features/base/settings'; import { updateSettings } from './react/features/base/settings';
import { import {
createLocalTracksF, createLocalTracksF,
destroyLocalTracks,
isLocalTrackMuted, isLocalTrackMuted,
replaceLocalTrack, replaceLocalTrack,
trackAdded, trackAdded,
@ -2466,7 +2467,11 @@ export default {
*/ */
hangup(requestFeedback = false) { hangup(requestFeedback = false) {
eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP); eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
APP.UI.removeLocalMedia();
APP.store.dispatch(destroyLocalTracks());
this._localTracksInitialized = false;
this.localVideo = null;
this.localAudio = null;
// Remove unnecessary event listeners from firing callbacks. // Remove unnecessary event listeners from firing callbacks.
if (this.deviceChangeListener) { if (this.deviceChangeListener) {
@ -2475,6 +2480,9 @@ export default {
this.deviceChangeListener); this.deviceChangeListener);
} }
APP.UI.removeAllListeners();
APP.remoteControl.removeAllListeners();
let requestFeedbackPromise; let requestFeedbackPromise;
if (requestFeedback) { if (requestFeedback) {
@ -2508,7 +2516,12 @@ export default {
leaveRoomAndDisconnect() { leaveRoomAndDisconnect() {
APP.store.dispatch(conferenceWillLeave(room)); APP.store.dispatch(conferenceWillLeave(room));
return room.leave().then(disconnect, disconnect); return room.leave()
.then(disconnect, disconnect)
.then(() => {
this._room = undefined;
room = undefined;
});
}, },
/** /**

View File

@ -18,7 +18,6 @@ import {
getLocalParticipant, getLocalParticipant,
showParticipantJoinedNotification showParticipantJoinedNotification
} from '../../react/features/base/participants'; } from '../../react/features/base/participants';
import { destroyLocalTracks } from '../../react/features/base/tracks';
import { toggleChat } from '../../react/features/chat'; import { toggleChat } from '../../react/features/chat';
import { openDisplayNamePrompt } from '../../react/features/display-name'; import { openDisplayNamePrompt } from '../../react/features/display-name';
import { setEtherpadHasInitialzied } from '../../react/features/etherpad'; import { setEtherpadHasInitialzied } from '../../react/features/etherpad';
@ -294,12 +293,6 @@ UI.start = function() {
UI.registerListeners UI.registerListeners
= () => UIListeners.forEach((value, key) => UI.addListener(key, value)); = () => UIListeners.forEach((value, key) => UI.addListener(key, value));
/**
* Unregister some UI event listeners.
*/
UI.unregisterListeners
= () => UIListeners.forEach((value, key) => UI.removeListener(key, value));
/** /**
* Setup some DOM event listeners. * Setup some DOM event listeners.
*/ */
@ -577,6 +570,15 @@ UI.addListener = function(type, listener) {
eventEmitter.on(type, listener); eventEmitter.on(type, listener);
}; };
/**
* Removes the all listeners for all events.
*
* @returns {void}
*/
UI.removeAllListeners = function() {
eventEmitter.removeAllListeners();
};
/** /**
* Removes the given listener for the given type of event. * Removes the given listener for the given type of event.
* *
@ -968,18 +970,6 @@ UI.setLocalRemoteControlActiveChanged = function() {
VideoLayout.setLocalRemoteControlActiveChanged(); VideoLayout.setLocalRemoteControlActiveChanged();
}; };
/**
* Remove media tracks and UI elements so the user no longer sees media in the
* UI. The intent is to provide a feeling that the meeting has ended.
*
* @returns {void}
*/
UI.removeLocalMedia = function() {
APP.store.dispatch(destroyLocalTracks());
VideoLayout.resetLargeVideo();
$('#videospace').hide();
};
// TODO: Export every function separately. For now there is no point of doing // TODO: Export every function separately. For now there is no point of doing
// this because we are importing everything. // this because we are importing everything.
export default UI; export default UI;

View File

@ -64,18 +64,6 @@ SharedVideoThumb.prototype.createContainer = function(spanId) {
return container; return container;
}; };
/**
* Removes RemoteVideo from the page.
*/
SharedVideoThumb.prototype.remove = function() {
logger.log('Remove shared video thumb', this.id);
// Remove whole container
if (this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
};
/** /**
* Sets the display name for the thumb. * Sets the display name for the thumb.
*/ */

View File

@ -108,6 +108,8 @@ export default class LargeVideoManager {
this._onVideoResolutionUpdate); this._onVideoResolutionUpdate);
this.removePresenceLabel(); this.removePresenceLabel();
this.$container.css({ display: 'none' });
} }
/** /**

View File

@ -437,33 +437,10 @@ RemoteVideo.prototype.updateConnectionStatusIndicator = function() {
* Removes RemoteVideo from the page. * Removes RemoteVideo from the page.
*/ */
RemoteVideo.prototype.remove = function() { RemoteVideo.prototype.remove = function() {
logger.log('Remove thumbnail', this.id); SmallVideo.prototype.remove.call(this);
this.removeAudioLevelIndicator();
const toolbarContainer
= this.container.querySelector('.videocontainer__toolbar');
if (toolbarContainer) {
ReactDOM.unmountComponentAtNode(toolbarContainer);
}
this.removeConnectionIndicator();
this.removeDisplayName();
this.removeAvatar();
this.removePresenceLabel(); this.removePresenceLabel();
this._unmountIndicators();
this.removeRemoteVideoMenu(); this.removeRemoteVideoMenu();
// Remove whole container
if (this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
}; };
RemoteVideo.prototype.waitForPlayback = function(streamElement, stream) { RemoteVideo.prototype.waitForPlayback = function(streamElement, stream) {

View File

@ -745,6 +745,37 @@ SmallVideo.prototype.initBrowserSpecificProperties = function() {
} }
}; };
/**
* Cleans up components on {@code SmallVideo} and removes itself from the DOM.
*
* @returns {void}
*/
SmallVideo.prototype.remove = function() {
logger.log('Remove thumbnail', this.id);
this.removeAudioLevelIndicator();
const toolbarContainer
= this.container.querySelector('.videocontainer__toolbar');
if (toolbarContainer) {
ReactDOM.unmountComponentAtNode(toolbarContainer);
}
this.removeConnectionIndicator();
this.removeDisplayName();
this.removeAvatar();
this._unmountIndicators();
// Remove whole container
if (this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
};
/** /**
* Helper function for re-rendering multiple react components of the small * Helper function for re-rendering multiple react components of the small
* video. * video.
@ -938,4 +969,5 @@ SmallVideo.prototype._onPopoverHover = function(popoverIsHovered) {
this.updateView(); this.updateView();
}; };
export default SmallVideo; export default SmallVideo;

View File

@ -132,18 +132,6 @@ const VideoLayout = {
this.registerListeners(); this.registerListeners();
}, },
/**
* Cleans up any existing largeVideo instance.
*
* @returns {void}
*/
resetLargeVideo() {
if (largeVideo) {
largeVideo.destroy();
}
largeVideo = null;
},
/** /**
* Registering listeners for UI events in Video layout component. * Registering listeners for UI events in Video layout component.
* *
@ -154,8 +142,18 @@ const VideoLayout = {
onLocalFlipXChanged); onLocalFlipXChanged);
}, },
/**
* Cleans up state of this singleton {@code VideoLayout}.
*
* @returns {void}
*/
reset() {
this._resetLargeVideo();
this._resetFilmstrip();
},
initLargeVideo() { initLargeVideo() {
this.resetLargeVideo(); this._resetLargeVideo();
largeVideo = new LargeVideoManager(eventEmitter); largeVideo = new LargeVideoManager(eventEmitter);
if (localFlipX) { if (localFlipX) {
@ -1165,6 +1163,40 @@ const VideoLayout = {
); );
}, },
/**
* Cleans up any existing largeVideo instance.
*
* @private
* @returns {void}
*/
_resetLargeVideo() {
if (largeVideo) {
largeVideo.destroy();
}
largeVideo = null;
},
/**
* Cleans up filmstrip state. While a separate {@code Filmstrip} exists, its
* implementation is mainly for querying and manipulating the DOM while
* state mostly remains in {@code VideoLayout}.
*
* @private
* @returns {void}
*/
_resetFilmstrip() {
Object.keys(remoteVideos).forEach(remoteVideoId => {
this.removeParticipantContainer(remoteVideoId);
delete remoteVideos[remoteVideoId];
});
if (localVideoThumbnail) {
localVideoThumbnail.remove();
localVideoThumbnail = null;
}
},
/** /**
* Triggers an update of large video if the passed in participant is * Triggers an update of large video if the passed in participant is
* currently displayed on large video. * currently displayed on large video.

View File

@ -13,6 +13,15 @@
*/ */
export const ADD_MESSAGE = Symbol('ADD_MESSAGE'); export const ADD_MESSAGE = Symbol('ADD_MESSAGE');
/**
* The type of the action which signals to remove all saved chat messages.
*
* {
* type: CLEAR_MESSAGES
* }
*/
export const CLEAR_MESSAGES = Symbol('CLEAR_MESSAGES');
/** /**
* The type of the action which signals a send a chat message to everyone in the * The type of the action which signals a send a chat message to everyone in the
* conference. * conference.

View File

@ -1,6 +1,9 @@
import { ADD_MESSAGE, SEND_MESSAGE, TOGGLE_CHAT } from './actionTypes'; import {
ADD_MESSAGE,
/* eslint-disable max-params */ CLEAR_MESSAGES,
SEND_MESSAGE,
TOGGLE_CHAT
} from './actionTypes';
/** /**
* Adds a chat message to the collection of messages. * Adds a chat message to the collection of messages.
@ -31,6 +34,19 @@ export function addMessage(messageDetails) {
}; };
} }
/**
* Removes all stored chat messages.
*
* @returns {{
* type: CLEAR_MESSAGES
* }}
*/
export function clearMessages() {
return {
type: CLEAR_MESSAGES
};
}
/** /**
* Sends a chat message to everyone in the conference. * Sends a chat message to everyone in the conference.
* *

View File

@ -3,7 +3,7 @@
import UIUtil from '../../../modules/UI/util/UIUtil'; import UIUtil from '../../../modules/UI/util/UIUtil';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app'; import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
import { CONFERENCE_JOINED } from '../base/conference'; import { CONFERENCE_JOINED, CONFERENCE_WILL_LEAVE } from '../base/conference';
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet'; import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { getParticipantById } from '../base/participants'; import { getParticipantById } from '../base/participants';
import { MiddlewareRegistry } from '../base/redux'; import { MiddlewareRegistry } from '../base/redux';
@ -11,7 +11,7 @@ import { playSound, registerSound, unregisterSound } from '../base/sounds';
import { isButtonEnabled, showToolbox } from '../toolbox'; import { isButtonEnabled, showToolbox } from '../toolbox';
import { SEND_MESSAGE } from './actionTypes'; import { SEND_MESSAGE } from './actionTypes';
import { addMessage } from './actions'; import { addMessage, clearMessages } from './actions';
import { INCOMING_MSG_SOUND_ID } from './constants'; import { INCOMING_MSG_SOUND_ID } from './constants';
import { INCOMING_MSG_SOUND_FILE } from './sounds'; import { INCOMING_MSG_SOUND_FILE } from './sounds';
@ -46,6 +46,10 @@ MiddlewareRegistry.register(store => next => action => {
|| _addChatMsgListener(action.conference, store); || _addChatMsgListener(action.conference, store);
break; break;
case CONFERENCE_WILL_LEAVE:
store.dispatch(clearMessages());
break;
case SEND_MESSAGE: case SEND_MESSAGE:
if (typeof APP !== 'undefined') { if (typeof APP !== 'undefined') {
const { conference } = store.getState()['features/base/conference']; const { conference } = store.getState()['features/base/conference'];

View File

@ -2,7 +2,7 @@
import { ReducerRegistry } from '../base/redux'; import { ReducerRegistry } from '../base/redux';
import { ADD_MESSAGE, TOGGLE_CHAT } from './actionTypes'; import { ADD_MESSAGE, CLEAR_MESSAGES, TOGGLE_CHAT } from './actionTypes';
const DEFAULT_STATE = { const DEFAULT_STATE = {
isOpen: false, isOpen: false,
@ -33,6 +33,13 @@ ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
}; };
} }
case CLEAR_MESSAGES:
return {
...state,
lastReadMessage: undefined,
messages: []
};
case TOGGLE_CHAT: case TOGGLE_CHAT:
return { return {
...state, ...state,

View File

@ -182,7 +182,6 @@ class Conference extends Component<Props> {
* @inheritdoc * @inheritdoc
*/ */
componentWillUnmount() { componentWillUnmount() {
APP.UI.unregisterListeners();
APP.UI.unbindEvents(); APP.UI.unbindEvents();
FULL_SCREEN_EVENTS.forEach(name => FULL_SCREEN_EVENTS.forEach(name =>

View File

@ -3,7 +3,7 @@
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout.js'; import VideoLayout from '../../../modules/UI/videolayout/VideoLayout.js';
import UIEvents from '../../../service/UI/UIEvents'; import UIEvents from '../../../service/UI/UIEvents';
import { CONFERENCE_JOINED } from '../base/conference'; import { CONFERENCE_JOINED, CONFERENCE_WILL_LEAVE } from '../base/conference';
import { import {
DOMINANT_SPEAKER_CHANGED, DOMINANT_SPEAKER_CHANGED,
PARTICIPANT_JOINED, PARTICIPANT_JOINED,
@ -40,6 +40,10 @@ MiddlewareRegistry.register(store => next => action => {
VideoLayout.mucJoined(); VideoLayout.mucJoined();
break; break;
case CONFERENCE_WILL_LEAVE:
VideoLayout.reset();
break;
case PARTICIPANT_JOINED: case PARTICIPANT_JOINED:
if (!action.participant.local) { if (!action.participant.local) {
VideoLayout.addRemoteParticipantContainer( VideoLayout.addRemoteParticipantContainer(