* ref(display-name): do not pass in display name The component gets the state itself from redux. * fix(display-name): do not default name to placeholder name The web display name component supports inline editing of the name. Problems can occur when the displayed name differs from the actual saved name, because participants without a display name, including the local user, have a different, default display name displayed. So when editing starts, the input field is populated with the default name. To workaround such while supporting fetching the display name using mapStateToProps, pass in both the name which should be shown and the name value saved in settings. * ref(display-name): rename methods
285 lines
8.5 KiB
JavaScript
285 lines
8.5 KiB
JavaScript
/* global $, config, interfaceConfig, APP */
|
|
|
|
/* eslint-disable no-unused-vars */
|
|
import React, { Component } from 'react';
|
|
import ReactDOM from 'react-dom';
|
|
import { Provider } from 'react-redux';
|
|
|
|
import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
|
|
import { VideoTrack } from '../../../react/features/base/media';
|
|
import {
|
|
getAvatarURLByParticipantId
|
|
} from '../../../react/features/base/participants';
|
|
import { updateSettings } from '../../../react/features/base/settings';
|
|
import { getLocalVideoTrack } from '../../../react/features/base/tracks';
|
|
import { shouldDisplayTileView } from '../../../react/features/video-layout';
|
|
/* eslint-enable no-unused-vars */
|
|
|
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
|
|
|
import UIEvents from '../../../service/UI/UIEvents';
|
|
import SmallVideo from './SmallVideo';
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function LocalVideo(VideoLayout, emitter, streamEndedCallback) {
|
|
this.videoSpanId = 'localVideoContainer';
|
|
this.streamEndedCallback = streamEndedCallback;
|
|
this.container = this.createContainer();
|
|
this.$container = $(this.container);
|
|
this.updateDOMLocation();
|
|
|
|
this.localVideoId = null;
|
|
this.bindHoverHandler();
|
|
if (config.enableLocalVideoFlip) {
|
|
this._buildContextMenu();
|
|
}
|
|
this.isLocal = true;
|
|
this.emitter = emitter;
|
|
this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP
|
|
? 'left top' : 'top center';
|
|
|
|
Object.defineProperty(this, 'id', {
|
|
get() {
|
|
return APP.conference.getMyUserId();
|
|
}
|
|
});
|
|
this.initBrowserSpecificProperties();
|
|
|
|
SmallVideo.call(this, VideoLayout);
|
|
|
|
// Set default display name.
|
|
this.updateDisplayName();
|
|
|
|
// Initialize the avatar display with an avatar url selected from the redux
|
|
// state. Redux stores the local user with a hardcoded participant id of
|
|
// 'local' if no id has been assigned yet.
|
|
this.avatarChanged(
|
|
getAvatarURLByParticipantId(APP.store.getState(), this.id));
|
|
|
|
this.addAudioLevelIndicator();
|
|
this.updateIndicators();
|
|
|
|
this.container.onclick = this._onContainerClick;
|
|
this.container.ondblclick = this._onContainerDoubleClick;
|
|
}
|
|
|
|
LocalVideo.prototype = Object.create(SmallVideo.prototype);
|
|
LocalVideo.prototype.constructor = LocalVideo;
|
|
|
|
LocalVideo.prototype.createContainer = function() {
|
|
const containerSpan = document.createElement('span');
|
|
|
|
containerSpan.classList.add('videocontainer');
|
|
containerSpan.id = this.videoSpanId;
|
|
|
|
containerSpan.innerHTML = `
|
|
<div class = 'videocontainer__background'></div>
|
|
<span id = 'localVideoWrapper'></span>
|
|
<div class = 'videocontainer__toolbar'></div>
|
|
<div class = 'videocontainer__toptoolbar'></div>
|
|
<div class = 'videocontainer__hoverOverlay'></div>
|
|
<div class = 'displayNameContainer'></div>
|
|
<div class = 'avatar-container'></div>`;
|
|
|
|
return containerSpan;
|
|
};
|
|
|
|
/**
|
|
* Triggers re-rendering of the display name using current instance state.
|
|
*
|
|
* @returns {void}
|
|
*/
|
|
LocalVideo.prototype.updateDisplayName = function() {
|
|
if (!this.container) {
|
|
logger.warn(
|
|
`Unable to set displayName - ${this.videoSpanId
|
|
} does not exist`);
|
|
|
|
return;
|
|
}
|
|
|
|
this._renderDisplayName({
|
|
allowEditing: APP.store.getState()['features/base/jwt'].isGuest,
|
|
displayNameSuffix: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME,
|
|
elementID: 'localDisplayName',
|
|
participantID: this.id
|
|
});
|
|
};
|
|
|
|
LocalVideo.prototype.changeVideo = function(stream) {
|
|
this.videoStream = stream;
|
|
|
|
this.localVideoId = `localVideo_${stream.getId()}`;
|
|
|
|
this._updateVideoElement();
|
|
|
|
// eslint-disable-next-line eqeqeq
|
|
const isVideo = stream.videoType != 'desktop';
|
|
const settings = APP.store.getState()['features/base/settings'];
|
|
|
|
this._enableDisableContextMenu(isVideo);
|
|
this.setFlipX(isVideo ? settings.localFlipX : false);
|
|
|
|
const endedHandler = () => {
|
|
const localVideoContainer
|
|
= document.getElementById('localVideoWrapper');
|
|
|
|
// Only remove if there is no video and not a transition state.
|
|
// Previous non-react logic created a new video element with each track
|
|
// removal whereas react reuses the video component so it could be the
|
|
// stream ended but a new one is being used.
|
|
if (localVideoContainer && this.videoStream.isEnded()) {
|
|
ReactDOM.unmountComponentAtNode(localVideoContainer);
|
|
}
|
|
|
|
this._notifyOfStreamEnded();
|
|
stream.off(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
|
|
};
|
|
|
|
stream.on(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
|
|
};
|
|
|
|
/**
|
|
* Notify any subscribers of the local video stream ending.
|
|
*
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
LocalVideo.prototype._notifyOfStreamEnded = function() {
|
|
if (this.streamEndedCallback) {
|
|
this.streamEndedCallback(this.id);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Shows or hides the local video container.
|
|
* @param {boolean} true to make the local video container visible, false
|
|
* otherwise
|
|
*/
|
|
LocalVideo.prototype.setVisible = function(visible) {
|
|
|
|
// We toggle the hidden class as an indication to other interested parties
|
|
// that this container has been hidden on purpose.
|
|
this.$container.toggleClass('hidden');
|
|
|
|
// We still show/hide it as we need to overwrite the style property if we
|
|
// want our action to take effect. Toggling the display property through
|
|
// the above css class didn't succeed in overwriting the style.
|
|
if (visible) {
|
|
this.$container.show();
|
|
} else {
|
|
this.$container.hide();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Sets the flipX state of the video.
|
|
* @param val {boolean} true for flipped otherwise false;
|
|
*/
|
|
LocalVideo.prototype.setFlipX = function(val) {
|
|
this.emitter.emit(UIEvents.LOCAL_FLIPX_CHANGED, val);
|
|
if (!this.localVideoId) {
|
|
return;
|
|
}
|
|
if (val) {
|
|
this.selectVideoElement().addClass('flipVideoX');
|
|
} else {
|
|
this.selectVideoElement().removeClass('flipVideoX');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Builds the context menu for the local video.
|
|
*/
|
|
LocalVideo.prototype._buildContextMenu = function() {
|
|
$.contextMenu({
|
|
selector: `#${this.videoSpanId}`,
|
|
zIndex: 10000,
|
|
items: {
|
|
flip: {
|
|
name: 'Flip',
|
|
callback: () => {
|
|
const { store } = APP;
|
|
const val = !store.getState()['features/base/settings']
|
|
.localFlipX;
|
|
|
|
this.setFlipX(val);
|
|
store.dispatch(updateSettings({
|
|
localFlipX: val
|
|
}));
|
|
}
|
|
}
|
|
},
|
|
events: {
|
|
show(options) {
|
|
options.items.flip.name
|
|
= APP.translation.generateTranslationHTML(
|
|
'videothumbnail.flip');
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Enables or disables the context menu for the local video.
|
|
* @param enable {boolean} true for enable, false for disable
|
|
*/
|
|
LocalVideo.prototype._enableDisableContextMenu = function(enable) {
|
|
if (this.$container.contextMenu) {
|
|
this.$container.contextMenu(enable);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Places the {@code LocalVideo} in the DOM based on the current video layout.
|
|
*
|
|
* @returns {void}
|
|
*/
|
|
LocalVideo.prototype.updateDOMLocation = function() {
|
|
if (!this.container) {
|
|
return;
|
|
}
|
|
|
|
if (this.container.parentElement) {
|
|
this.container.parentElement.removeChild(this.container);
|
|
}
|
|
|
|
const appendTarget = shouldDisplayTileView(APP.store.getState())
|
|
? document.getElementById('localVideoTileViewContainer')
|
|
: document.getElementById('filmstripLocalVideoThumbnail');
|
|
|
|
appendTarget && appendTarget.appendChild(this.container);
|
|
|
|
this._updateVideoElement();
|
|
};
|
|
|
|
/**
|
|
* Renders the React Element for displaying video in {@code LocalVideo}.
|
|
*
|
|
*/
|
|
LocalVideo.prototype._updateVideoElement = function() {
|
|
const localVideoContainer = document.getElementById('localVideoWrapper');
|
|
const videoTrack
|
|
= getLocalVideoTrack(APP.store.getState()['features/base/tracks']);
|
|
|
|
ReactDOM.render(
|
|
<Provider store = { APP.store }>
|
|
<VideoTrack
|
|
id = 'localVideo_container'
|
|
videoTrack = { videoTrack } />
|
|
</Provider>,
|
|
localVideoContainer
|
|
);
|
|
|
|
// Ensure the video gets play() called on it. This may be necessary in the
|
|
// case where the local video container was moved and re-attached, in which
|
|
// case video does not autoplay.
|
|
const video = this.container.querySelector('video');
|
|
|
|
video && video.play();
|
|
};
|
|
|
|
export default LocalVideo;
|