Compare commits
2 Commits
jitihouse/
...
perf-test
Author | SHA1 | Date |
---|---|---|
Hristo Terezov | ffeb88677a | |
Hristo Terezov | b2ddf55d44 |
|
@ -699,10 +699,6 @@ UI.showExtensionInlineInstallationDialog = function(callback) {
|
|||
});
|
||||
};
|
||||
|
||||
UI.updateDevicesAvailability = function(id, devices) {
|
||||
VideoLayout.setDeviceAvailabilityIcons(id, devices);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show shared video.
|
||||
* @param {string} id the id of the sender of the command
|
||||
|
|
|
@ -7,7 +7,15 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
|
|||
/**
|
||||
*
|
||||
*/
|
||||
export default function SharedVideoThumb(participant, videoType, VideoLayout) {
|
||||
export default class SharedVideoThumb extends SmallVideo {
|
||||
/**
|
||||
*
|
||||
* @param {*} participant
|
||||
* @param {*} videoType
|
||||
* @param {*} VideoLayout
|
||||
*/
|
||||
constructor(participant, videoType, VideoLayout) {
|
||||
super(VideoLayout);
|
||||
this.id = participant.id;
|
||||
|
||||
this.url = participant.id;
|
||||
|
@ -17,25 +25,22 @@ export default function SharedVideoThumb(participant, videoType, VideoLayout) {
|
|||
this.$container = $(this.container);
|
||||
|
||||
this.bindHoverHandler();
|
||||
SmallVideo.call(this, VideoLayout);
|
||||
this.isVideoMuted = true;
|
||||
this.updateDisplayName();
|
||||
|
||||
this.container.onclick = this._onContainerClick;
|
||||
}
|
||||
SharedVideoThumb.prototype = Object.create(SmallVideo.prototype);
|
||||
SharedVideoThumb.prototype.constructor = SharedVideoThumb;
|
||||
|
||||
/**
|
||||
* hide display name
|
||||
*
|
||||
*/
|
||||
// eslint-disable-next-line no-empty-function
|
||||
SharedVideoThumb.prototype.setDeviceAvailabilityIcons = function() {};
|
||||
initializeAvatar() {} // eslint-disable-line no-empty-function
|
||||
|
||||
// eslint-disable-next-line no-empty-function
|
||||
SharedVideoThumb.prototype.initializeAvatar = function() {};
|
||||
|
||||
SharedVideoThumb.prototype.createContainer = function(spanId) {
|
||||
/**
|
||||
*
|
||||
* @param {*} spanId
|
||||
*/
|
||||
createContainer(spanId) {
|
||||
const container = document.createElement('span');
|
||||
|
||||
container.id = spanId;
|
||||
|
@ -61,14 +66,14 @@ SharedVideoThumb.prototype.createContainer = function(spanId) {
|
|||
remoteVideosContainer.insertBefore(container, localVideoContainer);
|
||||
|
||||
return container;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers re-rendering of the display name using current instance state.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
SharedVideoThumb.prototype.updateDisplayName = function() {
|
||||
updateDisplayName() {
|
||||
if (!this.container) {
|
||||
logger.warn(`Unable to set displayName - ${this.videoSpanId
|
||||
} does not exist`);
|
||||
|
@ -80,4 +85,5 @@ SharedVideoThumb.prototype.updateDisplayName = function() {
|
|||
elementID: `${this.videoSpanId}_name`,
|
||||
participantID: this.id
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,11 +64,9 @@ const Filmstrip = {
|
|||
return this._calculateThumbnailSizeForTileView();
|
||||
}
|
||||
|
||||
const availableSizes = this.calculateAvailableSize();
|
||||
const width = availableSizes.availableWidth;
|
||||
const height = availableSizes.availableHeight;
|
||||
const { availableWidth, availableHeight } = this.calculateAvailableSize();
|
||||
|
||||
return this.calculateThumbnailSizeFromAvailable(width, height);
|
||||
return this.calculateThumbnailSizeFromAvailable(availableWidth, availableHeight);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -80,8 +78,7 @@ const Filmstrip = {
|
|||
calculateAvailableSize() {
|
||||
const state = APP.store.getState();
|
||||
const currentLayout = getCurrentLayout(state);
|
||||
const isHorizontalFilmstripView
|
||||
= currentLayout === LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW;
|
||||
const isHorizontalFilmstripView = currentLayout === LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW;
|
||||
|
||||
/**
|
||||
* If the videoAreaAvailableWidth is set we use this one to calculate
|
||||
|
@ -100,7 +97,6 @@ const Filmstrip = {
|
|||
|
||||
let availableHeight = interfaceConfig.FILM_STRIP_MAX_HEIGHT;
|
||||
let availableWidth = videoAreaAvailableWidth;
|
||||
|
||||
const thumbs = this.getThumbs(true);
|
||||
|
||||
// If local thumb is not hidden
|
||||
|
@ -149,15 +145,11 @@ const Filmstrip = {
|
|||
);
|
||||
}
|
||||
|
||||
const maxHeight
|
||||
|
||||
// If the MAX_HEIGHT property hasn't been specified
|
||||
// we have the static value.
|
||||
= Math.min(interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120,
|
||||
availableHeight);
|
||||
const maxHeight = Math.min(interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120, availableHeight);
|
||||
|
||||
availableHeight
|
||||
= Math.min(maxHeight, window.innerHeight - 18);
|
||||
availableHeight = Math.min(maxHeight, window.innerHeight - 18);
|
||||
|
||||
return {
|
||||
availableHeight,
|
||||
|
@ -239,13 +231,10 @@ const Filmstrip = {
|
|||
* availableHeight/h > availableWidth/totalWidth otherwise 2) is true
|
||||
*/
|
||||
|
||||
const remoteThumbsInRow = interfaceConfig.VERTICAL_FILMSTRIP
|
||||
? 0 : this.getThumbs(true).remoteThumbs.length;
|
||||
const remoteLocalWidthRatio = interfaceConfig.REMOTE_THUMBNAIL_RATIO
|
||||
/ interfaceConfig.LOCAL_THUMBNAIL_RATIO;
|
||||
const lW = Math.min(availableWidth
|
||||
/ ((remoteLocalWidthRatio * remoteThumbsInRow) + 1), availableHeight
|
||||
* interfaceConfig.LOCAL_THUMBNAIL_RATIO);
|
||||
const remoteThumbsInRow = interfaceConfig.VERTICAL_FILMSTRIP ? 0 : this.getThumbs(true).remoteThumbs.length;
|
||||
const remoteLocalWidthRatio = interfaceConfig.REMOTE_THUMBNAIL_RATIO / interfaceConfig.LOCAL_THUMBNAIL_RATIO;
|
||||
const lW = Math.min(availableWidth / ((remoteLocalWidthRatio * remoteThumbsInRow) + 1),
|
||||
availableHeight * interfaceConfig.LOCAL_THUMBNAIL_RATIO);
|
||||
const h = lW / interfaceConfig.LOCAL_THUMBNAIL_RATIO;
|
||||
|
||||
const remoteVideoWidth = lW * remoteLocalWidthRatio;
|
||||
|
@ -333,18 +322,12 @@ const Filmstrip = {
|
|||
if (shouldDisplayTileView(state)) {
|
||||
// The size of the side margins for each tile as set in CSS.
|
||||
const sideMargins = 10 * 2;
|
||||
const {
|
||||
columns,
|
||||
rows
|
||||
} = getTileViewGridDimensions(state, getMaxColumnCount());
|
||||
const { columns, rows } = getTileViewGridDimensions(state, getMaxColumnCount());
|
||||
const hasOverflow = rows > columns;
|
||||
|
||||
// Width is set so that the flex layout can automatically wrap
|
||||
// tiles onto new rows.
|
||||
this.filmstripRemoteVideos.css({
|
||||
width: (local.thumbWidth * columns) + (columns * sideMargins)
|
||||
});
|
||||
|
||||
this.filmstripRemoteVideos.css({ width: (local.thumbWidth * columns) + (columns * sideMargins) });
|
||||
this.filmstripRemoteVideos.toggleClass('has-overflow', hasOverflow);
|
||||
} else {
|
||||
this.filmstripRemoteVideos.css('width', '');
|
||||
|
|
|
@ -20,7 +20,15 @@ import SmallVideo from './SmallVideo';
|
|||
/**
|
||||
*
|
||||
*/
|
||||
function LocalVideo(VideoLayout, emitter, streamEndedCallback) {
|
||||
export default class LocalVideo extends SmallVideo {
|
||||
/**
|
||||
*
|
||||
* @param {*} VideoLayout
|
||||
* @param {*} emitter
|
||||
* @param {*} streamEndedCallback
|
||||
*/
|
||||
constructor(VideoLayout, emitter, streamEndedCallback) {
|
||||
super(VideoLayout);
|
||||
this.videoSpanId = 'localVideoContainer';
|
||||
this.streamEndedCallback = streamEndedCallback;
|
||||
this.container = this.createContainer();
|
||||
|
@ -44,8 +52,6 @@ function LocalVideo(VideoLayout, emitter, streamEndedCallback) {
|
|||
});
|
||||
this.initBrowserSpecificProperties();
|
||||
|
||||
SmallVideo.call(this, VideoLayout);
|
||||
|
||||
// Set default display name.
|
||||
this.updateDisplayName();
|
||||
|
||||
|
@ -60,10 +66,10 @@ function LocalVideo(VideoLayout, emitter, streamEndedCallback) {
|
|||
this.container.onclick = this._onContainerClick;
|
||||
}
|
||||
|
||||
LocalVideo.prototype = Object.create(SmallVideo.prototype);
|
||||
LocalVideo.prototype.constructor = LocalVideo;
|
||||
|
||||
LocalVideo.prototype.createContainer = function() {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
createContainer() {
|
||||
const containerSpan = document.createElement('span');
|
||||
|
||||
containerSpan.classList.add('videocontainer');
|
||||
|
@ -79,14 +85,14 @@ LocalVideo.prototype.createContainer = function() {
|
|||
<div class = 'avatar-container'></div>`;
|
||||
|
||||
return containerSpan;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers re-rendering of the display name using current instance state.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
LocalVideo.prototype.updateDisplayName = function() {
|
||||
updateDisplayName() {
|
||||
if (!this.container) {
|
||||
logger.warn(
|
||||
`Unable to set displayName - ${this.videoSpanId
|
||||
|
@ -101,13 +107,15 @@ LocalVideo.prototype.updateDisplayName = function() {
|
|||
elementID: 'localDisplayName',
|
||||
participantID: this.id
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
LocalVideo.prototype.changeVideo = function(stream) {
|
||||
/**
|
||||
*
|
||||
* @param {*} stream
|
||||
*/
|
||||
changeVideo(stream) {
|
||||
this.videoStream = stream;
|
||||
|
||||
this.localVideoId = `localVideo_${stream.getId()}`;
|
||||
|
||||
this._updateVideoElement();
|
||||
|
||||
// eslint-disable-next-line eqeqeq
|
||||
|
@ -134,7 +142,7 @@ LocalVideo.prototype.changeVideo = function(stream) {
|
|||
};
|
||||
|
||||
stream.on(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify any subscribers of the local video stream ending.
|
||||
|
@ -142,19 +150,18 @@ LocalVideo.prototype.changeVideo = function(stream) {
|
|||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
LocalVideo.prototype._notifyOfStreamEnded = function() {
|
||||
_notifyOfStreamEnded() {
|
||||
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) {
|
||||
|
||||
setVisible(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');
|
||||
|
@ -173,7 +180,7 @@ LocalVideo.prototype.setVisible = function(visible) {
|
|||
* Sets the flipX state of the video.
|
||||
* @param val {boolean} true for flipped otherwise false;
|
||||
*/
|
||||
LocalVideo.prototype.setFlipX = function(val) {
|
||||
setFlipX(val) {
|
||||
this.emitter.emit(UIEvents.LOCAL_FLIPX_CHANGED, val);
|
||||
if (!this.localVideoId) {
|
||||
return;
|
||||
|
@ -183,12 +190,12 @@ LocalVideo.prototype.setFlipX = function(val) {
|
|||
} else {
|
||||
this.selectVideoElement().removeClass('flipVideoX');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the context menu for the local video.
|
||||
*/
|
||||
LocalVideo.prototype._buildContextMenu = function() {
|
||||
_buildContextMenu() {
|
||||
$.contextMenu({
|
||||
selector: `#${this.videoSpanId}`,
|
||||
zIndex: 10000,
|
||||
|
@ -215,28 +222,27 @@ LocalVideo.prototype._buildContextMenu = function() {
|
|||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the context menu for the local video.
|
||||
* @param enable {boolean} true for enable, false for disable
|
||||
*/
|
||||
LocalVideo.prototype._enableDisableContextMenu = function(enable) {
|
||||
_enableDisableContextMenu(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() {
|
||||
updateDOMLocation() {
|
||||
if (!this.container) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.container.parentElement) {
|
||||
this.container.parentElement.removeChild(this.container);
|
||||
}
|
||||
|
@ -246,15 +252,14 @@ LocalVideo.prototype.updateDOMLocation = function() {
|
|||
: document.getElementById('filmstripLocalVideoThumbnail');
|
||||
|
||||
appendTarget && appendTarget.appendChild(this.container);
|
||||
|
||||
this._updateVideoElement();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the React Element for displaying video in {@code LocalVideo}.
|
||||
*
|
||||
*/
|
||||
LocalVideo.prototype._updateVideoElement = function() {
|
||||
_updateVideoElement() {
|
||||
const localVideoContainer = document.getElementById('localVideoWrapper');
|
||||
const videoTrack
|
||||
= getLocalVideoTrack(APP.store.getState()['features/base/tracks']);
|
||||
|
@ -274,6 +279,5 @@ LocalVideo.prototype._updateVideoElement = function() {
|
|||
const video = this.container.querySelector('video');
|
||||
|
||||
video && !config.testing?.noAutoPlayVideo && video.play();
|
||||
};
|
||||
|
||||
export default LocalVideo;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import { Provider } from 'react-redux';
|
|||
import { I18nextProvider } from 'react-i18next';
|
||||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||
|
||||
import { createThumbnailOffsetParentIsNullEvent, sendAnalytics } from '../../../react/features/analytics';
|
||||
import { i18next } from '../../../react/features/base/i18n';
|
||||
import {
|
||||
JitsiParticipantConnectionStatus
|
||||
|
@ -33,25 +32,57 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
|
|||
import SmallVideo from './SmallVideo';
|
||||
import UIUtils from '../util/UIUtil';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} spanId
|
||||
*/
|
||||
function createContainer(spanId) {
|
||||
const container = document.createElement('span');
|
||||
|
||||
container.id = spanId;
|
||||
container.className = 'videocontainer';
|
||||
|
||||
container.innerHTML = `
|
||||
<div class = 'videocontainer__background'></div>
|
||||
<div class = 'videocontainer__toptoolbar'></div>
|
||||
<div class = 'videocontainer__toolbar'></div>
|
||||
<div class = 'videocontainer__hoverOverlay'></div>
|
||||
<div class = 'displayNameContainer'></div>
|
||||
<div class = 'avatar-container'></div>
|
||||
<div class ='presence-label-container'></div>
|
||||
<span class = 'remotevideomenu'></span>`;
|
||||
|
||||
const remoteVideosContainer
|
||||
= document.getElementById('filmstripRemoteVideosContainer');
|
||||
const localVideoContainer
|
||||
= document.getElementById('localVideoTileViewContainer');
|
||||
|
||||
remoteVideosContainer.insertBefore(container, localVideoContainer);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default class RemoteVideo extends SmallVideo {
|
||||
/**
|
||||
* Creates new instance of the <tt>RemoteVideo</tt>.
|
||||
* @param user {JitsiParticipant} the user for whom remote video instance will
|
||||
* be created.
|
||||
* @param {VideoLayout} VideoLayout the video layout instance.
|
||||
* @param {EventEmitter} emitter the event emitter which will be used by
|
||||
* the new instance to emit events.
|
||||
* @constructor
|
||||
*/
|
||||
function RemoteVideo(user, VideoLayout, emitter) {
|
||||
constructor(user, VideoLayout) {
|
||||
super(VideoLayout);
|
||||
|
||||
this.user = user;
|
||||
this.id = user.getId();
|
||||
this.emitter = emitter;
|
||||
this.videoSpanId = `participant_${this.id}`;
|
||||
SmallVideo.call(this, VideoLayout);
|
||||
|
||||
this._audioStreamElement = null;
|
||||
this._supportsRemoteControl = false;
|
||||
this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP
|
||||
? 'left bottom' : 'top center';
|
||||
this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP ? 'left bottom' : 'top center';
|
||||
this.addRemoteVideoContainer();
|
||||
this.updateIndicators();
|
||||
this.updateDisplayName();
|
||||
|
@ -92,25 +123,20 @@ function RemoteVideo(user, VideoLayout, emitter) {
|
|||
this.container.onclick = this._onContainerClick;
|
||||
}
|
||||
|
||||
RemoteVideo.prototype = Object.create(SmallVideo.prototype);
|
||||
RemoteVideo.prototype.constructor = RemoteVideo;
|
||||
|
||||
RemoteVideo.prototype.addRemoteVideoContainer = function() {
|
||||
this.container = RemoteVideo.createContainer(this.videoSpanId);
|
||||
/**
|
||||
*
|
||||
*/
|
||||
addRemoteVideoContainer() {
|
||||
this.container = createContainer(this.videoSpanId);
|
||||
this.$container = $(this.container);
|
||||
|
||||
this.initBrowserSpecificProperties();
|
||||
|
||||
this.updateRemoteVideoMenu();
|
||||
|
||||
this.VideoLayout.resizeThumbnails(true);
|
||||
|
||||
this.addAudioLevelIndicator();
|
||||
|
||||
this.addPresenceLabel();
|
||||
|
||||
return this.container;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether current video is considered hovered. Currently it is hovered
|
||||
|
@ -119,13 +145,9 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
|
|||
* @private
|
||||
* NOTE: extends SmallVideo's method
|
||||
*/
|
||||
RemoteVideo.prototype._isHovered = function() {
|
||||
const isHovered = SmallVideo.prototype._isHovered.call(this)
|
||||
|| this.popupMenuIsHovered;
|
||||
|
||||
|
||||
return isHovered;
|
||||
};
|
||||
_isHovered() {
|
||||
return super._isHovered() || this.popupMenuIsHovered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the popup menu content.
|
||||
|
@ -133,7 +155,7 @@ RemoteVideo.prototype._isHovered = function() {
|
|||
* @returns {Element|*} the constructed element, containing popup menu items
|
||||
* @private
|
||||
*/
|
||||
RemoteVideo.prototype._generatePopupContent = function() {
|
||||
_generatePopupContent() {
|
||||
if (interfaceConfig.filmStripOnly) {
|
||||
return;
|
||||
}
|
||||
|
@ -163,15 +185,13 @@ RemoteVideo.prototype._generatePopupContent = function() {
|
|||
}
|
||||
}
|
||||
|
||||
const initialVolumeValue
|
||||
= this._audioStreamElement && this._audioStreamElement.volume;
|
||||
const initialVolumeValue = this._audioStreamElement && this._audioStreamElement.volume;
|
||||
|
||||
// hide volume when in silent mode
|
||||
const onVolumeChange = APP.store.getState()['features/base/config'].startSilent
|
||||
? undefined : this._setAudioVolume;
|
||||
const onVolumeChange
|
||||
= APP.store.getState()['features/base/config'].startSilent ? undefined : this._setAudioVolume;
|
||||
const { isModerator } = APP.conference;
|
||||
const participantID = this.id;
|
||||
|
||||
const currentLayout = getCurrentLayout(APP.store.getState());
|
||||
let remoteMenuPosition;
|
||||
|
||||
|
@ -202,11 +222,14 @@ RemoteVideo.prototype._generatePopupContent = function() {
|
|||
</I18nextProvider>
|
||||
</Provider>,
|
||||
remoteVideoMenuContainer);
|
||||
};
|
||||
}
|
||||
|
||||
RemoteVideo.prototype._onRemoteVideoMenuDisplay = function() {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
_onRemoteVideoMenuDisplay() {
|
||||
this.updateRemoteVideoMenu();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the remote control active status for the remote video.
|
||||
|
@ -214,46 +237,43 @@ RemoteVideo.prototype._onRemoteVideoMenuDisplay = function() {
|
|||
* @param {boolean} isActive - The new remote control active status.
|
||||
* @returns {void}
|
||||
*/
|
||||
RemoteVideo.prototype.setRemoteControlActiveStatus = function(isActive) {
|
||||
setRemoteControlActiveStatus(isActive) {
|
||||
this._isRemoteControlSessionActive = isActive;
|
||||
this.updateRemoteVideoMenu();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the remote control supported value and initializes or updates the menu
|
||||
* depending on the remote control is supported or not.
|
||||
* @param {boolean} isSupported
|
||||
*/
|
||||
RemoteVideo.prototype.setRemoteControlSupport = function(isSupported = false) {
|
||||
setRemoteControlSupport(isSupported = false) {
|
||||
if (this._supportsRemoteControl === isSupported) {
|
||||
return;
|
||||
}
|
||||
this._supportsRemoteControl = isSupported;
|
||||
this.updateRemoteVideoMenu();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests permissions for remote control session.
|
||||
*/
|
||||
RemoteVideo.prototype._requestRemoteControlPermissions = function() {
|
||||
APP.remoteControl.controller.requestPermissions(
|
||||
this.id, this.VideoLayout.getLargeVideoWrapper()).then(result => {
|
||||
_requestRemoteControlPermissions() {
|
||||
APP.remoteControl.controller.requestPermissions(this.id, this.VideoLayout.getLargeVideoWrapper())
|
||||
.then(result => {
|
||||
if (result === null) {
|
||||
return;
|
||||
}
|
||||
this.updateRemoteVideoMenu();
|
||||
APP.UI.messageHandler.notify(
|
||||
'dialog.remoteControlTitle',
|
||||
result === false ? 'dialog.remoteControlDeniedMessage'
|
||||
: 'dialog.remoteControlAllowedMessage',
|
||||
{ user: this.user.getDisplayName()
|
||||
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME }
|
||||
result === false ? 'dialog.remoteControlDeniedMessage' : 'dialog.remoteControlAllowedMessage',
|
||||
{ user: this.user.getDisplayName() || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME }
|
||||
);
|
||||
if (result === true) {
|
||||
// the remote control permissions has been granted
|
||||
// pin the controlled participant
|
||||
const pinnedParticipant
|
||||
= getPinnedParticipant(APP.store.getState()) || {};
|
||||
const pinnedParticipant = getPinnedParticipant(APP.store.getState()) || {};
|
||||
const pinnedId = pinnedParticipant.id;
|
||||
|
||||
if (pinnedId !== this.id) {
|
||||
|
@ -266,57 +286,54 @@ RemoteVideo.prototype._requestRemoteControlPermissions = function() {
|
|||
APP.UI.messageHandler.notify(
|
||||
'dialog.remoteControlTitle',
|
||||
'dialog.remoteControlErrorMessage',
|
||||
{ user: this.user.getDisplayName()
|
||||
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME }
|
||||
{ user: this.user.getDisplayName() || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME }
|
||||
);
|
||||
});
|
||||
this.updateRemoteVideoMenu();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops remote control session.
|
||||
*/
|
||||
RemoteVideo.prototype._stopRemoteControl = function() {
|
||||
_stopRemoteControl() {
|
||||
// send message about stopping
|
||||
APP.remoteControl.controller.stop();
|
||||
this.updateRemoteVideoMenu();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the remote participant's volume level.
|
||||
*
|
||||
* @param {int} newVal - The value to set the slider to.
|
||||
*/
|
||||
RemoteVideo.prototype._setAudioVolume = function(newVal) {
|
||||
_setAudioVolume(newVal) {
|
||||
if (this._audioStreamElement) {
|
||||
this._audioStreamElement.volume = newVal;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the remote video menu.
|
||||
*
|
||||
* @param isMuted the new muted state to update to
|
||||
*/
|
||||
RemoteVideo.prototype.updateRemoteVideoMenu = function(isMuted) {
|
||||
|
||||
updateRemoteVideoMenu(isMuted) {
|
||||
if (typeof isMuted !== 'undefined') {
|
||||
this.isAudioMuted = isMuted;
|
||||
}
|
||||
|
||||
this._generatePopupContent();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @override
|
||||
*/
|
||||
RemoteVideo.prototype.setVideoMutedView = function(isMuted) {
|
||||
SmallVideo.prototype.setVideoMutedView.call(this, isMuted);
|
||||
setVideoMutedView(isMuted) {
|
||||
super.setVideoMutedView(isMuted);
|
||||
|
||||
// Update 'mutedWhileDisconnected' flag
|
||||
this._figureOutMutedWhileDisconnected();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Figures out the value of {@link #mutedWhileDisconnected} flag by taking into
|
||||
|
@ -324,7 +341,7 @@ RemoteVideo.prototype.setVideoMutedView = function(isMuted) {
|
|||
*
|
||||
* @private
|
||||
*/
|
||||
RemoteVideo.prototype._figureOutMutedWhileDisconnected = function() {
|
||||
_figureOutMutedWhileDisconnected() {
|
||||
const isActive = this.isConnectionActive();
|
||||
|
||||
if (!isActive && this.isVideoMuted) {
|
||||
|
@ -332,7 +349,7 @@ RemoteVideo.prototype._figureOutMutedWhileDisconnected = function() {
|
|||
} else if (isActive && !this.isVideoMuted) {
|
||||
this.mutedWhileDisconnected = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the remote stream element corresponding to the given stream and
|
||||
|
@ -341,32 +358,28 @@ RemoteVideo.prototype._figureOutMutedWhileDisconnected = function() {
|
|||
* @param stream the MediaStream
|
||||
* @param isVideo <tt>true</tt> if given <tt>stream</tt> is a video one.
|
||||
*/
|
||||
RemoteVideo.prototype.removeRemoteStreamElement = function(stream) {
|
||||
removeRemoteStreamElement(stream) {
|
||||
if (!this.container) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isVideo = stream.isVideoTrack();
|
||||
|
||||
const elementID = SmallVideo.getStreamElementID(stream);
|
||||
const select = $(`#${elementID}`);
|
||||
|
||||
select.remove();
|
||||
|
||||
if (isVideo) {
|
||||
this.wasVideoPlayed = false;
|
||||
}
|
||||
|
||||
logger.info(`${isVideo ? 'Video' : 'Audio'
|
||||
} removed ${this.id}`, select);
|
||||
|
||||
logger.info(`${isVideo ? 'Video' : 'Audio'} removed ${this.id}`, select);
|
||||
|
||||
if (stream === this.videoStream) {
|
||||
this.videoStream = null;
|
||||
}
|
||||
|
||||
this.updateView();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the remote user associated with this <tt>RemoteVideo</tt>
|
||||
|
@ -375,10 +388,9 @@ RemoteVideo.prototype.removeRemoteStreamElement = function(stream) {
|
|||
* @return {boolean} <tt>true</tt> if the user's connection is fine or
|
||||
* <tt>false</tt> otherwise.
|
||||
*/
|
||||
RemoteVideo.prototype.isConnectionActive = function() {
|
||||
return this.user.getConnectionStatus()
|
||||
=== JitsiParticipantConnectionStatus.ACTIVE;
|
||||
};
|
||||
isConnectionActive() {
|
||||
return this.user.getConnectionStatus() === JitsiParticipantConnectionStatus.ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* The remote video is considered "playable" once the stream has started
|
||||
|
@ -393,34 +405,31 @@ RemoteVideo.prototype.isConnectionActive = function() {
|
|||
* @inheritdoc
|
||||
* @override
|
||||
*/
|
||||
RemoteVideo.prototype.isVideoPlayable = function() {
|
||||
const connectionState
|
||||
= APP.conference.getParticipantConnectionStatus(this.id);
|
||||
isVideoPlayable() {
|
||||
const connectionState = APP.conference.getParticipantConnectionStatus(this.id);
|
||||
|
||||
return SmallVideo.prototype.isVideoPlayable.call(this)
|
||||
return super.isVideoPlayable()
|
||||
&& this.hasVideoStarted()
|
||||
&& (connectionState === JitsiParticipantConnectionStatus.ACTIVE
|
||||
|| (connectionState === JitsiParticipantConnectionStatus.INTERRUPTED
|
||||
&& !this.mutedWhileDisconnected));
|
||||
};
|
||||
|| (connectionState === JitsiParticipantConnectionStatus.INTERRUPTED && !this.mutedWhileDisconnected));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
RemoteVideo.prototype.updateView = function() {
|
||||
updateView() {
|
||||
this.$container.toggleClass('audio-only', APP.conference.isAudioOnly());
|
||||
|
||||
this.updateConnectionStatusIndicator();
|
||||
|
||||
// This must be called after 'updateConnectionStatusIndicator' because it
|
||||
// affects the display mode by modifying 'mutedWhileDisconnected' flag
|
||||
SmallVideo.prototype.updateView.call(this);
|
||||
};
|
||||
super.updateView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI to reflect user's connectivity status.
|
||||
*/
|
||||
RemoteVideo.prototype.updateConnectionStatusIndicator = function() {
|
||||
updateConnectionStatusIndicator() {
|
||||
const connectionStatus = this.user.getConnectionStatus();
|
||||
|
||||
logger.debug(`${this.id} thumbnail connection status: ${connectionStatus}`);
|
||||
|
@ -430,29 +439,29 @@ RemoteVideo.prototype.updateConnectionStatusIndicator = function() {
|
|||
this._figureOutMutedWhileDisconnected();
|
||||
this.updateConnectionStatus(connectionStatus);
|
||||
|
||||
const isInterrupted
|
||||
= connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED;
|
||||
const isInterrupted = connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED;
|
||||
|
||||
// Toggle thumbnail video problem filter
|
||||
|
||||
this.selectVideoElement().toggleClass(
|
||||
'videoThumbnailProblemFilter', isInterrupted);
|
||||
this.$avatar().toggleClass(
|
||||
'videoThumbnailProblemFilter', isInterrupted);
|
||||
};
|
||||
this.selectVideoElement().toggleClass('videoThumbnailProblemFilter', isInterrupted);
|
||||
this.$avatar().toggleClass('videoThumbnailProblemFilter', isInterrupted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes RemoteVideo from the page.
|
||||
*/
|
||||
RemoteVideo.prototype.remove = function() {
|
||||
SmallVideo.prototype.remove.call(this);
|
||||
|
||||
remove() {
|
||||
super.remove();
|
||||
this.removePresenceLabel();
|
||||
this.removeRemoteVideoMenu();
|
||||
};
|
||||
|
||||
RemoteVideo.prototype.waitForPlayback = function(streamElement, stream) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} streamElement
|
||||
* @param {*} stream
|
||||
*/
|
||||
waitForPlayback(streamElement, stream) {
|
||||
const webRtcStream = stream.getOriginalStream();
|
||||
const isVideo = stream.isVideoTrack();
|
||||
|
||||
|
@ -460,20 +469,15 @@ RemoteVideo.prototype.waitForPlayback = function(streamElement, stream) {
|
|||
return;
|
||||
}
|
||||
|
||||
const self = this;
|
||||
|
||||
// Triggers when video playback starts
|
||||
const onPlayingHandler = function() {
|
||||
self.wasVideoPlayed = true;
|
||||
self.VideoLayout.remoteVideoActive(streamElement, self.id);
|
||||
streamElement.onplaying = () => {
|
||||
this.wasVideoPlayed = true;
|
||||
this.VideoLayout.remoteVideoActive(streamElement, this.id);
|
||||
streamElement.onplaying = null;
|
||||
|
||||
// Refresh to show the video
|
||||
self.updateView();
|
||||
};
|
||||
|
||||
streamElement.onplaying = onPlayingHandler;
|
||||
this.updateView();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the video stream has started for this RemoteVideo instance.
|
||||
|
@ -481,11 +485,15 @@ RemoteVideo.prototype.waitForPlayback = function(streamElement, stream) {
|
|||
* @returns {boolean} true if this RemoteVideo has a video stream for which
|
||||
* the playback has been started.
|
||||
*/
|
||||
RemoteVideo.prototype.hasVideoStarted = function() {
|
||||
hasVideoStarted() {
|
||||
return this.wasVideoPlayed;
|
||||
};
|
||||
}
|
||||
|
||||
RemoteVideo.prototype.addRemoteStreamElement = function(stream) {
|
||||
/**
|
||||
*
|
||||
* @param {*} stream
|
||||
*/
|
||||
addRemoteStreamElement(stream) {
|
||||
if (!this.container) {
|
||||
logger.debug('Not attaching remote stream due to no container');
|
||||
|
||||
|
@ -516,30 +524,6 @@ RemoteVideo.prototype.addRemoteStreamElement = function(stream) {
|
|||
this.waitForPlayback(streamElement, stream);
|
||||
stream.attach(streamElement);
|
||||
|
||||
// TODO: Remove once we verify that this.container.offsetParent === null was the reason for not attached video
|
||||
// streams to the thumbnail.
|
||||
if (isVideo && this.container.offsetParent === null) {
|
||||
sendAnalytics(createThumbnailOffsetParentIsNullEvent(this.id));
|
||||
const parentNodesDisplayProps = [
|
||||
'#filmstripRemoteVideosContainer',
|
||||
'#filmstripRemoteVideos',
|
||||
'#remoteVideos',
|
||||
'.filmstrip',
|
||||
'#videospace',
|
||||
'#videoconference_page',
|
||||
'#react'
|
||||
].map(selector => `${selector} - ${$(selector).css('display')}`);
|
||||
const videoConferencePageParent = $('#videoconference_page').parent();
|
||||
const reactDiv = document.getElementById('react');
|
||||
|
||||
parentNodesDisplayProps.push(
|
||||
`${videoConferencePageParent.attr('class')} - ${videoConferencePageParent.css('display')}`);
|
||||
parentNodesDisplayProps.push(`this.container - ${this.$container.css('display')}`);
|
||||
logger.debug(`this.container.offsetParent is null [user: ${this.id}, ${
|
||||
parentNodesDisplayProps.join(', ')}, #react.offsetParent - ${
|
||||
reactDiv && reactDiv.offsetParent !== null ? 'not null' : 'null'}]`);
|
||||
}
|
||||
|
||||
if (!isVideo) {
|
||||
this._audioStreamElement = streamElement;
|
||||
|
||||
|
@ -548,17 +532,16 @@ RemoteVideo.prototype.addRemoteStreamElement = function(stream) {
|
|||
// slider.
|
||||
this.updateRemoteVideoMenu();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers re-rendering of the display name using current instance state.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
RemoteVideo.prototype.updateDisplayName = function() {
|
||||
updateDisplayName() {
|
||||
if (!this.container) {
|
||||
logger.warn(`Unable to set displayName - ${this.videoSpanId
|
||||
} does not exist`);
|
||||
logger.warn(`Unable to set displayName - ${this.videoSpanId} does not exist`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -567,7 +550,7 @@ RemoteVideo.prototype.updateDisplayName = function() {
|
|||
elementID: `${this.videoSpanId}_name`,
|
||||
participantID: this.id
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes remote video menu element from video element identified by
|
||||
|
@ -575,14 +558,14 @@ RemoteVideo.prototype.updateDisplayName = function() {
|
|||
*
|
||||
* @param videoElementId the id of local or remote video element.
|
||||
*/
|
||||
RemoteVideo.prototype.removeRemoteVideoMenu = function() {
|
||||
removeRemoteVideoMenu() {
|
||||
const menuSpan = this.$container.find('.remotevideomenu');
|
||||
|
||||
if (menuSpan.length) {
|
||||
ReactDOM.unmountComponentAtNode(menuSpan.get(0));
|
||||
menuSpan.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Mounts the {@code PresenceLabel} for displaying the participant's current
|
||||
|
@ -590,9 +573,8 @@ RemoteVideo.prototype.removeRemoteVideoMenu = function() {
|
|||
*
|
||||
* @return {void}
|
||||
*/
|
||||
RemoteVideo.prototype.addPresenceLabel = function() {
|
||||
const presenceLabelContainer
|
||||
= this.container.querySelector('.presence-label-container');
|
||||
addPresenceLabel() {
|
||||
const presenceLabelContainer = this.container.querySelector('.presence-label-container');
|
||||
|
||||
if (presenceLabelContainer) {
|
||||
ReactDOM.render(
|
||||
|
@ -605,46 +587,18 @@ RemoteVideo.prototype.addPresenceLabel = function() {
|
|||
</Provider>,
|
||||
presenceLabelContainer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmounts the {@code PresenceLabel} component.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
RemoteVideo.prototype.removePresenceLabel = function() {
|
||||
const presenceLabelContainer
|
||||
= this.container.querySelector('.presence-label-container');
|
||||
removePresenceLabel() {
|
||||
const presenceLabelContainer = this.container.querySelector('.presence-label-container');
|
||||
|
||||
if (presenceLabelContainer) {
|
||||
ReactDOM.unmountComponentAtNode(presenceLabelContainer);
|
||||
}
|
||||
};
|
||||
|
||||
RemoteVideo.createContainer = function(spanId) {
|
||||
const container = document.createElement('span');
|
||||
|
||||
container.id = spanId;
|
||||
container.className = 'videocontainer';
|
||||
|
||||
container.innerHTML = `
|
||||
<div class = 'videocontainer__background'></div>
|
||||
<div class = 'videocontainer__toptoolbar'></div>
|
||||
<div class = 'videocontainer__toolbar'></div>
|
||||
<div class = 'videocontainer__hoverOverlay'></div>
|
||||
<div class = 'displayNameContainer'></div>
|
||||
<div class = 'avatar-container'></div>
|
||||
<div class ='presence-label-container'></div>
|
||||
<span class = 'remotevideomenu'></span>`;
|
||||
|
||||
const remoteVideosContainer
|
||||
= document.getElementById('filmstripRemoteVideosContainer');
|
||||
const localVideoContainer
|
||||
= document.getElementById('localVideoTileViewContainer');
|
||||
|
||||
remoteVideosContainer.insertBefore(container, localVideoContainer);
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
export default RemoteVideo;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,17 +8,14 @@ import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
|||
import { Provider } from 'react-redux';
|
||||
|
||||
import { i18next } from '../../../react/features/base/i18n';
|
||||
import { AudioLevelIndicator }
|
||||
from '../../../react/features/audio-level-indicator';
|
||||
import { AudioLevelIndicator } from '../../../react/features/audio-level-indicator';
|
||||
import { Avatar as AvatarDisplay } from '../../../react/features/base/avatar';
|
||||
import {
|
||||
getParticipantCount,
|
||||
getPinnedParticipant,
|
||||
pinParticipant
|
||||
} from '../../../react/features/base/participants';
|
||||
import {
|
||||
ConnectionIndicator
|
||||
} from '../../../react/features/connection-indicator';
|
||||
import { ConnectionIndicator } from '../../../react/features/connection-indicator';
|
||||
import { DisplayName } from '../../../react/features/display-name';
|
||||
import {
|
||||
AudioMutedIndicator,
|
||||
|
@ -79,10 +76,15 @@ const DISPLAY_VIDEO_WITH_NAME = 3;
|
|||
*/
|
||||
const DISPLAY_AVATAR_WITH_NAME = 4;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default class SmallVideo {
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
function SmallVideo(VideoLayout) {
|
||||
constructor(VideoLayout) {
|
||||
this._isModerator = false;
|
||||
this.isAudioMuted = false;
|
||||
this.hasAvatar = false;
|
||||
|
@ -117,8 +119,7 @@ function SmallVideo(VideoLayout) {
|
|||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
this._showConnectionIndicator
|
||||
= !interfaceConfig.CONNECTION_INDICATOR_DISABLED;
|
||||
this._showConnectionIndicator = !interfaceConfig.CONNECTION_INDICATOR_DISABLED;
|
||||
|
||||
/**
|
||||
* Whether or not the dominant speaker indicator should be displayed.
|
||||
|
@ -148,18 +149,19 @@ function SmallVideo(VideoLayout) {
|
|||
*
|
||||
* @returns the identifier of this small video
|
||||
*/
|
||||
SmallVideo.prototype.getId = function() {
|
||||
getId() {
|
||||
return this.id;
|
||||
};
|
||||
}
|
||||
|
||||
/* Indicates if this small video is currently visible.
|
||||
/**
|
||||
* Indicates if this small video is currently visible.
|
||||
*
|
||||
* @return <tt>true</tt> if this small video isn't currently visible and
|
||||
* <tt>false</tt> - otherwise.
|
||||
*/
|
||||
SmallVideo.prototype.isVisible = function() {
|
||||
isVisible() {
|
||||
return this.$container.is(':visible');
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of the video displayed by this instance.
|
||||
|
@ -168,9 +170,9 @@ SmallVideo.prototype.isVisible = function() {
|
|||
* lib-jitsi-meet.
|
||||
* @param videoType 'camera' or 'desktop', or 'sharedvideo'.
|
||||
*/
|
||||
SmallVideo.prototype.setVideoType = function(videoType) {
|
||||
setVideoType(videoType) {
|
||||
this.videoType = videoType;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the video displayed by this instance.
|
||||
|
@ -179,19 +181,16 @@ SmallVideo.prototype.setVideoType = function(videoType) {
|
|||
* lib-jitsi-meet.
|
||||
* @returns {String} 'camera', 'screen', 'sharedvideo', or undefined.
|
||||
*/
|
||||
SmallVideo.prototype.getVideoType = function() {
|
||||
getVideoType() {
|
||||
return this.videoType;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an audio or video element for a particular MediaStream.
|
||||
*/
|
||||
SmallVideo.createStreamElement = function(stream) {
|
||||
static createStreamElement(stream) {
|
||||
const isVideo = stream.isVideoTrack();
|
||||
|
||||
const element = isVideo
|
||||
? document.createElement('video')
|
||||
: document.createElement('audio');
|
||||
const element = isVideo ? document.createElement('video') : document.createElement('audio');
|
||||
|
||||
if (isVideo) {
|
||||
element.setAttribute('muted', 'true');
|
||||
|
@ -203,21 +202,19 @@ SmallVideo.createStreamElement = function(stream) {
|
|||
element.id = SmallVideo.getStreamElementID(stream);
|
||||
|
||||
return element;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element id for a particular MediaStream.
|
||||
*/
|
||||
SmallVideo.getStreamElementID = function(stream) {
|
||||
const isVideo = stream.isVideoTrack();
|
||||
|
||||
return (isVideo ? 'remoteVideo_' : 'remoteAudio_') + stream.getId();
|
||||
};
|
||||
static getStreamElementID(stream) {
|
||||
return (stream.isVideoTrack() ? 'remoteVideo_' : 'remoteAudio_') + stream.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures hoverIn/hoverOut handlers. Depends on connection indicator.
|
||||
*/
|
||||
SmallVideo.prototype.bindHoverHandler = function() {
|
||||
bindHoverHandler() {
|
||||
// Add hover handler
|
||||
this.$container.hover(
|
||||
() => {
|
||||
|
@ -231,28 +228,27 @@ SmallVideo.prototype.bindHoverHandler = function() {
|
|||
this.updateIndicators();
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmounts the ConnectionIndicator component.
|
||||
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.removeConnectionIndicator = function() {
|
||||
removeConnectionIndicator() {
|
||||
this._showConnectionIndicator = false;
|
||||
|
||||
this.updateIndicators();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the connectionStatus stat which displays in the ConnectionIndicator.
|
||||
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.updateConnectionStatus = function(connectionStatus) {
|
||||
updateConnectionStatus(connectionStatus) {
|
||||
this._connectionStatus = connectionStatus;
|
||||
this.updateIndicators();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows / hides the audio muted indicator over small videos.
|
||||
|
@ -260,10 +256,10 @@ SmallVideo.prototype.updateConnectionStatus = function(connectionStatus) {
|
|||
* @param {boolean} isMuted indicates if the muted element should be shown
|
||||
* or hidden
|
||||
*/
|
||||
SmallVideo.prototype.showAudioIndicator = function(isMuted) {
|
||||
showAudioIndicator(isMuted) {
|
||||
this.isAudioMuted = isMuted;
|
||||
this.updateStatusBar();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows video muted indicator over small videos and disables/enables avatar
|
||||
|
@ -272,12 +268,11 @@ SmallVideo.prototype.showAudioIndicator = function(isMuted) {
|
|||
* @param {boolean} isMuted indicates if we should set the view to muted view
|
||||
* or not
|
||||
*/
|
||||
SmallVideo.prototype.setVideoMutedView = function(isMuted) {
|
||||
setVideoMutedView(isMuted) {
|
||||
this.isVideoMuted = isMuted;
|
||||
this.updateView();
|
||||
|
||||
this.updateStatusBar();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or updates the ReactElement for displaying status indicators about
|
||||
|
@ -285,9 +280,8 @@ SmallVideo.prototype.setVideoMutedView = function(isMuted) {
|
|||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.updateStatusBar = function() {
|
||||
const statusBarContainer
|
||||
= this.container.querySelector('.videocontainer__toolbar');
|
||||
updateStatusBar() {
|
||||
const statusBarContainer = this.container.querySelector('.videocontainer__toolbar');
|
||||
|
||||
if (!statusBarContainer) {
|
||||
return;
|
||||
|
@ -322,22 +316,22 @@ SmallVideo.prototype.updateStatusBar = function() {
|
|||
</div>
|
||||
</I18nextProvider>,
|
||||
statusBarContainer);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the element indicating the moderator(owner) of the conference.
|
||||
*/
|
||||
SmallVideo.prototype.addModeratorIndicator = function() {
|
||||
addModeratorIndicator() {
|
||||
this._isModerator = true;
|
||||
this.updateStatusBar();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the element indicating the audio level of the participant.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.addAudioLevelIndicator = function() {
|
||||
addAudioLevelIndicator() {
|
||||
let audioLevelContainer = this._getAudioLevelContainer();
|
||||
|
||||
if (audioLevelContainer) {
|
||||
|
@ -347,22 +341,21 @@ SmallVideo.prototype.addAudioLevelIndicator = function() {
|
|||
audioLevelContainer = document.createElement('span');
|
||||
audioLevelContainer.className = 'audioindicator-container';
|
||||
this.container.appendChild(audioLevelContainer);
|
||||
|
||||
this.updateAudioLevelIndicator();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the element indicating the audio level of the participant.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.removeAudioLevelIndicator = function() {
|
||||
removeAudioLevelIndicator() {
|
||||
const audioLevelContainer = this._getAudioLevelContainer();
|
||||
|
||||
if (audioLevelContainer) {
|
||||
ReactDOM.unmountComponentAtNode(audioLevelContainer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the audio level for this small video.
|
||||
|
@ -370,16 +363,13 @@ SmallVideo.prototype.removeAudioLevelIndicator = function() {
|
|||
* @param lvl the new audio level to set
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.updateAudioLevelIndicator = function(lvl = 0) {
|
||||
updateAudioLevelIndicator(lvl = 0) {
|
||||
const audioLevelContainer = this._getAudioLevelContainer();
|
||||
|
||||
if (audioLevelContainer) {
|
||||
ReactDOM.render(
|
||||
<AudioLevelIndicator
|
||||
audioLevel = { lvl }/>,
|
||||
audioLevelContainer);
|
||||
ReactDOM.render(<AudioLevelIndicator audioLevel = { lvl }/>, audioLevelContainer);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Queries the component's DOM for the element that should be the parent to the
|
||||
|
@ -387,17 +377,17 @@ SmallVideo.prototype.updateAudioLevelIndicator = function(lvl = 0) {
|
|||
*
|
||||
* @returns {HTMLElement} The DOM element that holds the AudioLevelIndicator.
|
||||
*/
|
||||
SmallVideo.prototype._getAudioLevelContainer = function() {
|
||||
_getAudioLevelContainer() {
|
||||
return this.container.querySelector('.audioindicator-container');
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the element indicating the moderator(owner) of the conference.
|
||||
*/
|
||||
SmallVideo.prototype.removeModeratorIndicator = function() {
|
||||
removeModeratorIndicator() {
|
||||
this._isModerator = false;
|
||||
this.updateStatusBar();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an especially interesting function. A naive reader might think that
|
||||
|
@ -409,9 +399,9 @@ SmallVideo.prototype.removeModeratorIndicator = function() {
|
|||
* this function to access the video element via the 0th element of the returned
|
||||
* array (after checking its length of course!).
|
||||
*/
|
||||
SmallVideo.prototype.selectVideoElement = function() {
|
||||
selectVideoElement() {
|
||||
return $($(this.container).find('video')[0]);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the HTML image element which displays user's avatar.
|
||||
|
@ -419,9 +409,9 @@ SmallVideo.prototype.selectVideoElement = function() {
|
|||
* @return {jQuery|HTMLElement} a jQuery selector pointing to the HTML image
|
||||
* element which displays the user's avatar.
|
||||
*/
|
||||
SmallVideo.prototype.$avatar = function() {
|
||||
$avatar() {
|
||||
return this.$container.find('.avatar-container');
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display name element, which appears on the video thumbnail.
|
||||
|
@ -429,9 +419,9 @@ SmallVideo.prototype.$avatar = function() {
|
|||
* @return {jQuery} a jQuery selector pointing to the display name element of
|
||||
* the video thumbnail
|
||||
*/
|
||||
SmallVideo.prototype.$displayName = function() {
|
||||
$displayName() {
|
||||
return this.$container.find('.displayNameContainer');
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or updates the participant's display name that is shown over the
|
||||
|
@ -441,9 +431,8 @@ SmallVideo.prototype.$displayName = function() {
|
|||
* {@code DisplayName} component.
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype._renderDisplayName = function(props) {
|
||||
const displayNameContainer
|
||||
= this.container.querySelector('.displayNameContainer');
|
||||
_renderDisplayName(props) {
|
||||
const displayNameContainer = this.container.querySelector('.displayNameContainer');
|
||||
|
||||
if (displayNameContainer) {
|
||||
ReactDOM.render(
|
||||
|
@ -454,7 +443,7 @@ SmallVideo.prototype._renderDisplayName = function(props) {
|
|||
</Provider>,
|
||||
displayNameContainer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the component responsible for showing the participant's display name,
|
||||
|
@ -462,14 +451,13 @@ SmallVideo.prototype._renderDisplayName = function(props) {
|
|||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.removeDisplayName = function() {
|
||||
const displayNameContainer
|
||||
= this.container.querySelector('.displayNameContainer');
|
||||
removeDisplayName() {
|
||||
const displayNameContainer = this.container.querySelector('.displayNameContainer');
|
||||
|
||||
if (displayNameContainer) {
|
||||
ReactDOM.unmountComponentAtNode(displayNameContainer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables / disables the css responsible for focusing/pinning a video
|
||||
|
@ -477,7 +465,7 @@ SmallVideo.prototype.removeDisplayName = function() {
|
|||
*
|
||||
* @param isFocused indicates if the thumbnail should be focused/pinned or not
|
||||
*/
|
||||
SmallVideo.prototype.focus = function(isFocused) {
|
||||
focus(isFocused) {
|
||||
const focusedCssClass = 'videoContainerFocused';
|
||||
const isFocusClassEnabled = this.$container.hasClass(focusedCssClass);
|
||||
|
||||
|
@ -486,11 +474,14 @@ SmallVideo.prototype.focus = function(isFocused) {
|
|||
} else if (isFocused && !isFocusClassEnabled) {
|
||||
this.$container.addClass(focusedCssClass);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SmallVideo.prototype.hasVideo = function() {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
hasVideo() {
|
||||
return this.selectVideoElement().length !== 0;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the user associated with this <tt>SmallVideo</tt> is currently
|
||||
|
@ -499,9 +490,9 @@ SmallVideo.prototype.hasVideo = function() {
|
|||
* @return {boolean} <tt>true</tt> if the user is displayed on the large video
|
||||
* or <tt>false</tt> otherwise.
|
||||
*/
|
||||
SmallVideo.prototype.isCurrentlyOnLargeVideo = function() {
|
||||
isCurrentlyOnLargeVideo() {
|
||||
return this.VideoLayout.isCurrentlyOnLarge(this.id);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether there is a playable video stream available for the user
|
||||
|
@ -510,9 +501,9 @@ SmallVideo.prototype.isCurrentlyOnLargeVideo = function() {
|
|||
* @return {boolean} <tt>true</tt> if there is a playable video stream available
|
||||
* or <tt>false</tt> otherwise.
|
||||
*/
|
||||
SmallVideo.prototype.isVideoPlayable = function() {
|
||||
isVideoPlayable() {
|
||||
return this.videoStream && !this.isVideoMuted && !APP.conference.isAudioOnly();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines what should be display on the thumbnail.
|
||||
|
@ -520,8 +511,7 @@ SmallVideo.prototype.isVideoPlayable = function() {
|
|||
* @return {number} one of <tt>DISPLAY_VIDEO</tt>,<tt>DISPLAY_AVATAR</tt>
|
||||
* or <tt>DISPLAY_BLACKNESS_WITH_NAME</tt>.
|
||||
*/
|
||||
SmallVideo.prototype.selectDisplayMode = function(input) {
|
||||
|
||||
selectDisplayMode(input) {
|
||||
// Display name is always and only displayed when user is on the stage
|
||||
if (input.isCurrentlyOnLargeVideo && !input.tileViewEnabled) {
|
||||
return input.isVideoPlayable && !input.isAudioOnly ? DISPLAY_BLACKNESS_WITH_NAME : DISPLAY_AVATAR_WITH_NAME;
|
||||
|
@ -532,14 +522,14 @@ SmallVideo.prototype.selectDisplayMode = function(input) {
|
|||
|
||||
// check hovering and change state to avatar with name
|
||||
return input.isHovered ? DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes information that determine the display mode.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
SmallVideo.prototype.computeDisplayModeInput = function() {
|
||||
computeDisplayModeInput() {
|
||||
return {
|
||||
isCurrentlyOnLargeVideo: this.isCurrentlyOnLargeVideo(),
|
||||
isHovered: this._isHovered(),
|
||||
|
@ -554,7 +544,7 @@ SmallVideo.prototype.computeDisplayModeInput = function() {
|
|||
isVideoMuted: this.isVideoMuted,
|
||||
videoStreamMuted: this.videoStream ? this.videoStream.isMuted() : 'no stream'
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether current video is considered hovered. Currently it is hovered
|
||||
|
@ -562,16 +552,16 @@ SmallVideo.prototype.computeDisplayModeInput = function() {
|
|||
* indicator is shown(hovered).
|
||||
* @private
|
||||
*/
|
||||
SmallVideo.prototype._isHovered = function() {
|
||||
_isHovered() {
|
||||
return this.videoIsHovered || this._popoverIsHovered;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides or shows the user's avatar.
|
||||
* This update assumes that large video had been updated and we will
|
||||
* reflect it on this small video.
|
||||
*/
|
||||
SmallVideo.prototype.updateView = function() {
|
||||
updateView() {
|
||||
if (this.id) {
|
||||
// Init / refresh avatar
|
||||
this.initializeAvatar();
|
||||
|
@ -619,7 +609,7 @@ SmallVideo.prototype.updateView = function() {
|
|||
if (this.displayMode !== oldDisplayMode) {
|
||||
logger.debug(`Displaying ${displayModeString} for ${this.id}, data: [${JSON.stringify(displayModeInput)}]`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the react component displaying the avatar with the passed in avatar
|
||||
|
@ -627,7 +617,7 @@ SmallVideo.prototype.updateView = function() {
|
|||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.initializeAvatar = function() {
|
||||
initializeAvatar() {
|
||||
const thumbnail = this.$avatar().get(0);
|
||||
|
||||
this.hasAvatar = true;
|
||||
|
@ -645,7 +635,7 @@ SmallVideo.prototype.initializeAvatar = function() {
|
|||
thumbnail
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmounts any attached react components (particular the avatar image) from
|
||||
|
@ -653,19 +643,19 @@ SmallVideo.prototype.initializeAvatar = function() {
|
|||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.removeAvatar = function() {
|
||||
removeAvatar() {
|
||||
const thumbnail = this.$avatar().get(0);
|
||||
|
||||
if (thumbnail) {
|
||||
ReactDOM.unmountComponentAtNode(thumbnail);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows or hides the dominant speaker indicator.
|
||||
* @param show whether to show or hide.
|
||||
*/
|
||||
SmallVideo.prototype.showDominantSpeakerIndicator = function(show) {
|
||||
showDominantSpeakerIndicator(show) {
|
||||
// Don't create and show dominant speaker indicator if
|
||||
// DISABLE_DOMINANT_SPEAKER_INDICATOR is true
|
||||
if (interfaceConfig.DISABLE_DOMINANT_SPEAKER_INDICATOR) {
|
||||
|
@ -673,29 +663,25 @@ SmallVideo.prototype.showDominantSpeakerIndicator = function(show) {
|
|||
}
|
||||
|
||||
if (!this.container) {
|
||||
logger.warn(`Unable to set dominant speaker indicator - ${
|
||||
this.videoSpanId} does not exist`);
|
||||
logger.warn(`Unable to set dominant speaker indicator - ${this.videoSpanId} does not exist`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._showDominantSpeaker === show) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._showDominantSpeaker = show;
|
||||
|
||||
this.$container.toggleClass('active-speaker', this._showDominantSpeaker);
|
||||
|
||||
this.updateIndicators();
|
||||
this.updateView();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows or hides the raised hand indicator.
|
||||
* @param show whether to show or hide.
|
||||
*/
|
||||
SmallVideo.prototype.showRaisedHandIndicator = function(show) {
|
||||
showRaisedHandIndicator(show) {
|
||||
if (!this.container) {
|
||||
logger.warn(`Unable to raised hand indication - ${
|
||||
this.videoSpanId} does not exist`);
|
||||
|
@ -704,16 +690,15 @@ SmallVideo.prototype.showRaisedHandIndicator = function(show) {
|
|||
}
|
||||
|
||||
this._showRaisedHand = show;
|
||||
|
||||
this.updateIndicators();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for onresize events for this video, which will monitor for
|
||||
* resolution changes, will calculate the delay since the moment the listened
|
||||
* is added, and will fire a RESOLUTION_CHANGED event.
|
||||
*/
|
||||
SmallVideo.prototype.waitForResolutionChange = function() {
|
||||
waitForResolutionChange() {
|
||||
const beforeChange = window.performance.now();
|
||||
const videos = this.selectVideoElement();
|
||||
|
||||
|
@ -734,16 +719,12 @@ SmallVideo.prototype.waitForResolutionChange = function() {
|
|||
const emitter = this.VideoLayout.getEventEmitter();
|
||||
|
||||
if (emitter) {
|
||||
emitter.emit(
|
||||
UIEvents.RESOLUTION_CHANGED,
|
||||
this.getId(),
|
||||
`${oldWidth}x${oldHeight}`,
|
||||
`${video.videoWidth}x${video.videoHeight}`,
|
||||
delay);
|
||||
emitter.emit(UIEvents.RESOLUTION_CHANGED, this.getId(), `${oldWidth}x${oldHeight}`,
|
||||
`${video.videoWidth}x${video.videoHeight}`, delay);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initalizes any browser specific properties. Currently sets the overflow
|
||||
|
@ -757,25 +738,22 @@ SmallVideo.prototype.waitForResolutionChange = function() {
|
|||
* Setting this property for all browsers will result in broken audio levels,
|
||||
* which makes this a temporary solution, before reworking audio levels.
|
||||
*/
|
||||
SmallVideo.prototype.initBrowserSpecificProperties = function() {
|
||||
|
||||
initBrowserSpecificProperties() {
|
||||
const userAgent = window.navigator.userAgent;
|
||||
|
||||
if (userAgent.indexOf('QtWebEngine') > -1
|
||||
&& (userAgent.indexOf('Windows') > -1
|
||||
|| userAgent.indexOf('Linux') > -1)) {
|
||||
&& (userAgent.indexOf('Windows') > -1 || userAgent.indexOf('Linux') > -1)) {
|
||||
this.$container.css('overflow', 'hidden');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up components on {@code SmallVideo} and removes itself from the DOM.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.remove = function() {
|
||||
remove() {
|
||||
logger.log('Remove thumbnail', this.id);
|
||||
|
||||
this.removeAudioLevelIndicator();
|
||||
|
||||
const toolbarContainer
|
||||
|
@ -786,18 +764,15 @@ SmallVideo.prototype.remove = function() {
|
|||
}
|
||||
|
||||
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
|
||||
|
@ -805,11 +780,11 @@ SmallVideo.prototype.remove = function() {
|
|||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.rerender = function() {
|
||||
rerender() {
|
||||
this.updateIndicators();
|
||||
this.updateStatusBar();
|
||||
this.updateView();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the React element responsible for showing connection status, dominant
|
||||
|
@ -819,17 +794,15 @@ SmallVideo.prototype.rerender = function() {
|
|||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.updateIndicators = function() {
|
||||
const indicatorToolbar
|
||||
= this.container.querySelector('.videocontainer__toptoolbar');
|
||||
updateIndicators() {
|
||||
const indicatorToolbar = this.container.querySelector('.videocontainer__toptoolbar');
|
||||
|
||||
if (!indicatorToolbar) {
|
||||
return;
|
||||
}
|
||||
|
||||
const iconSize = UIUtil.getIndicatorFontSize();
|
||||
const showConnectionIndicator = this.videoIsHovered
|
||||
|| !interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED;
|
||||
const showConnectionIndicator = this.videoIsHovered || !interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED;
|
||||
const state = APP.store.getState();
|
||||
const currentLayout = getCurrentLayout(state);
|
||||
const participantCount = getParticipantCount(state);
|
||||
|
@ -878,7 +851,7 @@ SmallVideo.prototype.updateIndicators = function() {
|
|||
</Provider>,
|
||||
indicatorToolbar
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked when the thumbnail is clicked and potentially trigger
|
||||
|
@ -888,20 +861,19 @@ SmallVideo.prototype.updateIndicators = function() {
|
|||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype._onContainerClick = function(event) {
|
||||
_onContainerClick(event) {
|
||||
const triggerPin = this._shouldTriggerPin(event);
|
||||
|
||||
if (event.stopPropagation && triggerPin) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (triggerPin) {
|
||||
this.togglePin();
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a click event is targeted at certain elements which
|
||||
|
@ -911,7 +883,7 @@ SmallVideo.prototype._onContainerClick = function(event) {
|
|||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
SmallVideo.prototype._shouldTriggerPin = function(event) {
|
||||
_shouldTriggerPin(event) {
|
||||
// TODO Checking the classes is a workround to allow events to bubble into
|
||||
// the DisplayName component if it was clicked. React's synthetic events
|
||||
// will fire after jQuery handlers execute, so stop propogation at this
|
||||
|
@ -923,22 +895,19 @@ SmallVideo.prototype._shouldTriggerPin = function(event) {
|
|||
return $source.parents('.displayNameContainer').length === 0
|
||||
&& $source.parents('.popover').length === 0
|
||||
&& !event.target.classList.contains('popover');
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Pins the participant displayed by this thumbnail or unpins if already pinned.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.togglePin = function() {
|
||||
const pinnedParticipant
|
||||
= getPinnedParticipant(APP.store.getState()) || {};
|
||||
const participantIdToPin
|
||||
= pinnedParticipant && pinnedParticipant.id === this.id
|
||||
? null : this.id;
|
||||
togglePin() {
|
||||
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
|
||||
|
@ -947,14 +916,13 @@ SmallVideo.prototype.togglePin = function() {
|
|||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype._unmountIndicators = function() {
|
||||
const indicatorToolbar
|
||||
= this.container.querySelector('.videocontainer__toptoolbar');
|
||||
_unmountIndicators() {
|
||||
const indicatorToolbar = this.container.querySelector('.videocontainer__toptoolbar');
|
||||
|
||||
if (indicatorToolbar) {
|
||||
ReactDOM.unmountComponentAtNode(indicatorToolbar);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current state of the connection indicator popover being hovered.
|
||||
|
@ -964,10 +932,8 @@ SmallVideo.prototype._unmountIndicators = function() {
|
|||
* currently over the connection indicator popover.
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype._onPopoverHover = function(popoverIsHovered) {
|
||||
_onPopoverHover(popoverIsHovered) {
|
||||
this._popoverIsHovered = popoverIsHovered;
|
||||
this.updateView();
|
||||
};
|
||||
|
||||
|
||||
export default SmallVideo;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -166,27 +166,6 @@ const VideoLayout = {
|
|||
localVideoThumbnail.updateIndicators();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds or removes icons for not available camera and microphone.
|
||||
* @param resourceJid the jid of user
|
||||
* @param devices available devices
|
||||
*/
|
||||
setDeviceAvailabilityIcons(id, devices) {
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
localVideoThumbnail.setDeviceAvailabilityIcons(devices);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const video = remoteVideos[id];
|
||||
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
|
||||
video.setDeviceAvailabilityIcons(devices);
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows/hides local video.
|
||||
* @param {boolean} true to make the local video visible, false - otherwise
|
||||
|
@ -411,27 +390,20 @@ const VideoLayout = {
|
|||
/**
|
||||
* Resizes thumbnails.
|
||||
*/
|
||||
resizeThumbnails(
|
||||
forceUpdate = false,
|
||||
onComplete = null) {
|
||||
const { localVideo, remoteVideo }
|
||||
= Filmstrip.calculateThumbnailSize();
|
||||
resizeThumbnails(forceUpdate = false, onComplete = null) {
|
||||
const { localVideo, remoteVideo } = Filmstrip.calculateThumbnailSize();
|
||||
|
||||
Filmstrip.resizeThumbnails(localVideo, remoteVideo, forceUpdate);
|
||||
|
||||
if (shouldDisplayTileView(APP.store.getState())) {
|
||||
const height
|
||||
= (localVideo && localVideo.thumbHeight)
|
||||
|| (remoteVideo && remoteVideo.thumbnHeight)
|
||||
|| 0;
|
||||
const height = (localVideo && localVideo.thumbHeight) || (remoteVideo && remoteVideo.thumbnHeight) || 0;
|
||||
const qualityLevel = getNearestReceiverVideoQualityLevel(height);
|
||||
|
||||
APP.store.dispatch(setMaxReceiverVideoQuality(qualityLevel));
|
||||
}
|
||||
|
||||
localVideoThumbnail && localVideoThumbnail.rerender();
|
||||
Object.values(remoteVideos).forEach(
|
||||
remoteVideoThumbnail => remoteVideoThumbnail.rerender());
|
||||
Object.values(remoteVideos).forEach(remoteVideoThumbnail => remoteVideoThumbnail.rerender());
|
||||
|
||||
if (onComplete && typeof onComplete === 'function') {
|
||||
onComplete();
|
||||
|
|
|
@ -692,21 +692,6 @@ export function createSyncTrackStateEvent(mediaType, muted) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event that indicates the thumbnail offset parent is null.
|
||||
*
|
||||
* @param {string} id - The id of the user related to the thumbnail.
|
||||
* @returns {Object} The event in a format suitable for sending via sendAnalytics.
|
||||
*/
|
||||
export function createThumbnailOffsetParentIsNullEvent(id) {
|
||||
return {
|
||||
action: 'OffsetParentIsNull',
|
||||
attributes: {
|
||||
id
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event associated with a toolbar button being clicked/pressed. By
|
||||
* convention, where appropriate an attribute named 'enable' should be used to
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title></title>
|
||||
<script src="../../libs/lib-jitsi-meet.min.js?v=139"></script>
|
||||
<script src="libs/load-test-participant.min.js" ></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>Number of participants: <span id="participants">1</span></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,261 @@
|
|||
/* global $, JitsiMeetJS */
|
||||
import 'jquery';
|
||||
import parseURLParams from '../../react/features/base/config/parseURLParams';
|
||||
|
||||
const params = parseURLParams(window.location, false, 'hash');
|
||||
const { isHuman = false } = params;
|
||||
const {
|
||||
roomName = 'loadtest0',
|
||||
localAudio = isHuman,
|
||||
localVideo = isHuman,
|
||||
remoteVideo = isHuman,
|
||||
remoteAudio = isHuman
|
||||
} = params;
|
||||
|
||||
const options = {
|
||||
hosts: {
|
||||
domain: 'george-perf.jitsi.net',
|
||||
muc: 'conference.george-perf.jitsi.net'
|
||||
},
|
||||
bosh: '//george-perf.jitsi.net/http-bind',
|
||||
|
||||
// The name of client node advertised in XEP-0115 'c' stanza
|
||||
clientNode: 'http://jitsi.org/jitsimeet'
|
||||
};
|
||||
|
||||
const confOptions = {
|
||||
openBridgeChannel: 'websocket',
|
||||
testing: {
|
||||
testMode: true,
|
||||
noAutoPlayVideo: true
|
||||
},
|
||||
disableNS: true,
|
||||
disableAEC: true,
|
||||
gatherStats: true,
|
||||
callStatsID: false
|
||||
|
||||
};
|
||||
|
||||
let connection = null;
|
||||
|
||||
let isJoined = false;
|
||||
|
||||
let room = null;
|
||||
|
||||
let numParticipants = 1;
|
||||
|
||||
let localTracks = [];
|
||||
const remoteTracks = {};
|
||||
|
||||
window.APP = {
|
||||
get room() {
|
||||
return room;
|
||||
},
|
||||
get connection() {
|
||||
return connection;
|
||||
},
|
||||
get numParticipants() {
|
||||
return numParticipants;
|
||||
},
|
||||
get localTracks() {
|
||||
return localTracks;
|
||||
},
|
||||
get remoteTracks() {
|
||||
return remoteTracks;
|
||||
},
|
||||
get params() {
|
||||
return {
|
||||
roomName,
|
||||
localAudio,
|
||||
localVideo,
|
||||
remoteVideo,
|
||||
remoteAudio
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function setNumberOfParticipants() {
|
||||
$('#participants').text(numParticipants);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles local tracks.
|
||||
* @param tracks Array with JitsiTrack objects
|
||||
*/
|
||||
function onLocalTracks(tracks = []) {
|
||||
localTracks = tracks;
|
||||
for (let i = 0; i < localTracks.length; i++) {
|
||||
if (localTracks[i].getType() === 'video') {
|
||||
$('body').append(`<video autoplay='1' id='localVideo${i}' />`);
|
||||
localTracks[i].attach($(`#localVideo${i}`)[0]);
|
||||
} else {
|
||||
$('body').append(
|
||||
`<audio autoplay='1' muted='true' id='localAudio${i}' />`);
|
||||
localTracks[i].attach($(`#localAudio${i}`)[0]);
|
||||
}
|
||||
if (isJoined) {
|
||||
room.addTrack(localTracks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles remote tracks
|
||||
* @param track JitsiTrack object
|
||||
*/
|
||||
function onRemoteTrack(track) {
|
||||
if (track.isLocal()
|
||||
|| (track.getType() === 'video' && !remoteVideo) || (track.getType() === 'audio' && !remoteAudio)) {
|
||||
return;
|
||||
}
|
||||
const participant = track.getParticipantId();
|
||||
|
||||
if (!remoteTracks[participant]) {
|
||||
remoteTracks[participant] = [];
|
||||
}
|
||||
const idx = remoteTracks[participant].push(track);
|
||||
const id = participant + track.getType() + idx;
|
||||
|
||||
if (track.getType() === 'video') {
|
||||
$('body').append(`<video autoplay='1' id='${id}' />`);
|
||||
} else {
|
||||
$('body').append(`<audio autoplay='1' id='${id}' />`);
|
||||
}
|
||||
track.attach($(`#${id}`)[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* That function is executed when the conference is joined
|
||||
*/
|
||||
function onConferenceJoined() {
|
||||
isJoined = true;
|
||||
for (let i = 0; i < localTracks.length; i++) {
|
||||
room.addTrack(localTracks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
function onUserLeft(id) {
|
||||
numParticipants--;
|
||||
setNumberOfParticipants();
|
||||
if (!remoteTracks[id]) {
|
||||
return;
|
||||
}
|
||||
const tracks = remoteTracks[id];
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
const container = $(`#${id}${tracks[i].getType()}${i + 1}`)[0];
|
||||
|
||||
if (container) {
|
||||
tracks[i].detach(container);
|
||||
container.parentElement.removeChild(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* That function is called when connection is established successfully
|
||||
*/
|
||||
function onConnectionSuccess() {
|
||||
room = connection.initJitsiConference(roomName, confOptions);
|
||||
room.on(JitsiMeetJS.events.conference.TRACK_ADDED, onRemoteTrack);
|
||||
room.on(JitsiMeetJS.events.conference.CONFERENCE_JOINED, onConferenceJoined);
|
||||
room.on(JitsiMeetJS.events.conference.USER_JOINED, id => {
|
||||
numParticipants++;
|
||||
setNumberOfParticipants();
|
||||
remoteTracks[id] = [];
|
||||
});
|
||||
room.on(JitsiMeetJS.events.conference.USER_LEFT, onUserLeft);
|
||||
room.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when the connection fail.
|
||||
*/
|
||||
function onConnectionFailed() {
|
||||
console.error('Connection Failed!');
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when we disconnect.
|
||||
*/
|
||||
function disconnect() {
|
||||
console.log('disconnect!');
|
||||
connection.removeEventListener(
|
||||
JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED,
|
||||
onConnectionSuccess);
|
||||
connection.removeEventListener(
|
||||
JitsiMeetJS.events.connection.CONNECTION_FAILED,
|
||||
onConnectionFailed);
|
||||
connection.removeEventListener(
|
||||
JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED,
|
||||
disconnect);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function unload() {
|
||||
for (let i = 0; i < localTracks.length; i++) {
|
||||
localTracks[i].dispose();
|
||||
}
|
||||
room.leave();
|
||||
connection.disconnect();
|
||||
}
|
||||
|
||||
$(window).bind('beforeunload', unload);
|
||||
$(window).bind('unload', unload);
|
||||
|
||||
JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
|
||||
|
||||
const initOptions = {
|
||||
disableAudioLevels: true,
|
||||
|
||||
// The ID of the jidesha extension for Chrome.
|
||||
desktopSharingChromeExtId: 'mbocklcggfhnbahlnepmldehdhpjfcjp',
|
||||
|
||||
// Whether desktop sharing should be disabled on Chrome.
|
||||
desktopSharingChromeDisabled: true,
|
||||
|
||||
// The media sources to use when using screen sharing with the Chrome
|
||||
// extension.
|
||||
desktopSharingChromeSources: [ 'screen', 'window' ],
|
||||
|
||||
// Required version of Chrome extension
|
||||
desktopSharingChromeMinExtVersion: '0.1',
|
||||
|
||||
// Whether desktop sharing should be disabled on Firefox.
|
||||
desktopSharingFirefoxDisabled: true
|
||||
};
|
||||
|
||||
JitsiMeetJS.init(initOptions);
|
||||
|
||||
connection = new JitsiMeetJS.JitsiConnection(null, null, options);
|
||||
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED, onConnectionSuccess);
|
||||
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, onConnectionFailed);
|
||||
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, disconnect);
|
||||
connection.connect();
|
||||
|
||||
const devices = [];
|
||||
|
||||
if (localVideo) {
|
||||
devices.push('video');
|
||||
}
|
||||
if (localAudio) {
|
||||
devices.push('audio');
|
||||
}
|
||||
if (devices.length > 0) {
|
||||
JitsiMeetJS.createLocalTracks({ devices })
|
||||
.then(onLocalTracks)
|
||||
.catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"name": "jitsi-meet-load-test",
|
||||
"version": "0.0.0",
|
||||
"description": "A load test participant",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/jitsi/jitsi-meet"
|
||||
},
|
||||
"keywords": [
|
||||
"jingle",
|
||||
"webrtc",
|
||||
"xmpp",
|
||||
"browser"
|
||||
],
|
||||
"author": "",
|
||||
"readmeFilename": "../README.md",
|
||||
"dependencies": {
|
||||
"jquery": "3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.5.5",
|
||||
"@babel/plugin-proposal-class-properties": "7.1.0",
|
||||
"@babel/plugin-proposal-export-default-from": "7.0.0",
|
||||
"@babel/plugin-proposal-export-namespace-from": "7.0.0",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "7.4.4",
|
||||
"@babel/plugin-proposal-optional-chaining": "7.2.0",
|
||||
"@babel/plugin-transform-flow-strip-types": "7.0.0",
|
||||
"@babel/preset-env": "7.1.0",
|
||||
"@babel/preset-flow": "7.0.0",
|
||||
"@babel/runtime": "7.5.5",
|
||||
"babel-eslint": "10.0.1",
|
||||
"babel-loader": "8.0.4",
|
||||
"eslint": "5.6.1",
|
||||
"eslint-config-jitsi": "github:jitsi/eslint-config-jitsi#1.0.1",
|
||||
"eslint-plugin-flowtype": "2.50.3",
|
||||
"eslint-plugin-import": "2.14.0",
|
||||
"eslint-plugin-jsdoc": "3.8.0",
|
||||
"expose-loader": "0.7.5",
|
||||
"flow-bin": "0.104.0",
|
||||
"imports-loader": "0.7.1",
|
||||
"string-replace-loader": "2.1.1",
|
||||
"style-loader": "0.19.0",
|
||||
"webpack": "4.27.1",
|
||||
"webpack-bundle-analyzer": "3.4.1",
|
||||
"webpack-cli": "3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"build": "webpack -p"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/* global __dirname */
|
||||
|
||||
const process = require('process');
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
const analyzeBundle = process.argv.indexOf('--analyze-bundle') !== -1;
|
||||
const minimize
|
||||
= process.argv.indexOf('-p') !== -1
|
||||
|| process.argv.indexOf('--optimize-minimize') !== -1;
|
||||
|
||||
/**
|
||||
* Build a Performance configuration object for the given size.
|
||||
* See: https://webpack.js.org/configuration/performance/
|
||||
*/
|
||||
function getPerformanceHints(size) {
|
||||
return {
|
||||
hints: minimize ? 'error' : false,
|
||||
maxAssetSize: size,
|
||||
maxEntrypointSize: size
|
||||
};
|
||||
}
|
||||
|
||||
// The base Webpack configuration to bundle the JavaScript artifacts of
|
||||
// jitsi-meet such as app.bundle.js and external_api.js.
|
||||
const config = {
|
||||
devtool: 'source-map',
|
||||
mode: minimize ? 'production' : 'development',
|
||||
module: {
|
||||
rules: [ {
|
||||
// Transpile ES2015 (aka ES6) to ES5. Accept the JSX syntax by React
|
||||
// as well.
|
||||
|
||||
exclude: [
|
||||
new RegExp(`${__dirname}/node_modules/(?!js-utils)`)
|
||||
],
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
// XXX The require.resolve bellow solves failures to locate the
|
||||
// presets when lib-jitsi-meet, for example, is npm linked in
|
||||
// jitsi-meet.
|
||||
plugins: [
|
||||
require.resolve('@babel/plugin-transform-flow-strip-types'),
|
||||
require.resolve('@babel/plugin-proposal-class-properties'),
|
||||
require.resolve('@babel/plugin-proposal-export-default-from'),
|
||||
require.resolve('@babel/plugin-proposal-export-namespace-from'),
|
||||
require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'),
|
||||
require.resolve('@babel/plugin-proposal-optional-chaining')
|
||||
],
|
||||
presets: [
|
||||
[
|
||||
require.resolve('@babel/preset-env'),
|
||||
|
||||
// Tell babel to avoid compiling imports into CommonJS
|
||||
// so that webpack may do tree shaking.
|
||||
{
|
||||
modules: false,
|
||||
|
||||
// Specify our target browsers so no transpiling is
|
||||
// done unnecessarily. For browsers not specified
|
||||
// here, the ES2015+ profile will be used.
|
||||
targets: {
|
||||
chrome: 58,
|
||||
electron: 2,
|
||||
firefox: 54,
|
||||
safari: 11
|
||||
}
|
||||
|
||||
}
|
||||
],
|
||||
require.resolve('@babel/preset-flow'),
|
||||
require.resolve('@babel/preset-react')
|
||||
]
|
||||
},
|
||||
test: /\.jsx?$/
|
||||
}, {
|
||||
// Expose jquery as the globals $ and jQuery because it is expected
|
||||
// to be available in such a form by multiple jitsi-meet
|
||||
// dependencies including lib-jitsi-meet.
|
||||
|
||||
loader: 'expose-loader?$!expose-loader?jQuery',
|
||||
test: /\/node_modules\/jquery\/.*\.js$/
|
||||
}]
|
||||
},
|
||||
node: {
|
||||
// Allow the use of the real filename of the module being executed. By
|
||||
// default Webpack does not leak path-related information and provides a
|
||||
// value that is a mock (/index.js).
|
||||
__filename: true
|
||||
},
|
||||
optimization: {
|
||||
concatenateModules: minimize,
|
||||
minimize
|
||||
},
|
||||
output: {
|
||||
filename: `[name]${minimize ? '.min' : ''}.js`,
|
||||
path: `${__dirname}/libs`,
|
||||
publicPath: 'load-test/libs/',
|
||||
sourceMapFilename: `[name].${minimize ? 'min' : 'js'}.map`
|
||||
},
|
||||
plugins: [
|
||||
analyzeBundle
|
||||
&& new BundleAnalyzerPlugin({
|
||||
analyzerMode: 'disabled',
|
||||
generateStatsFile: true
|
||||
})
|
||||
].filter(Boolean),
|
||||
resolve: {
|
||||
alias: {
|
||||
jquery: `jquery/dist/jquery${minimize ? '.min' : ''}.js`
|
||||
},
|
||||
aliasFields: [
|
||||
'browser'
|
||||
],
|
||||
extensions: [
|
||||
'.web.js',
|
||||
|
||||
// Webpack defaults:
|
||||
'.js',
|
||||
'.json'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = [
|
||||
Object.assign({}, config, {
|
||||
entry: {
|
||||
'load-test-participant': './load-test-participant.js'
|
||||
},
|
||||
performance: getPerformanceHints(3 * 1024 * 1024)
|
||||
})
|
||||
];
|
||||
|
Loading…
Reference in New Issue