2017-10-10 23:31:40 +00:00
|
|
|
/* global $, APP, interfaceConfig */
|
2017-05-31 15:42:50 +00:00
|
|
|
|
|
|
|
/* eslint-disable no-unused-vars */
|
|
|
|
import React from 'react';
|
2017-06-19 16:01:44 +00:00
|
|
|
import ReactDOM from 'react-dom';
|
2017-07-31 23:33:22 +00:00
|
|
|
import { Provider } from 'react-redux';
|
2017-08-14 15:02:58 +00:00
|
|
|
import { I18nextProvider } from 'react-i18next';
|
2017-10-06 16:14:45 +00:00
|
|
|
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
2017-08-14 15:02:58 +00:00
|
|
|
|
|
|
|
import { i18next } from '../../../react/features/base/i18n';
|
2017-10-10 23:31:40 +00:00
|
|
|
import {
|
|
|
|
JitsiParticipantConnectionStatus
|
|
|
|
} from '../../../react/features/base/lib-jitsi-meet';
|
2018-05-18 19:59:07 +00:00
|
|
|
import {
|
|
|
|
getPinnedParticipant,
|
|
|
|
pinParticipant
|
|
|
|
} from '../../../react/features/base/participants';
|
2017-07-31 23:33:22 +00:00
|
|
|
import { PresenceLabel } from '../../../react/features/presence-status';
|
2017-05-31 15:42:50 +00:00
|
|
|
import {
|
|
|
|
REMOTE_CONTROL_MENU_STATES,
|
2017-08-14 15:02:58 +00:00
|
|
|
RemoteVideoMenuTriggerButton
|
2017-05-31 15:42:50 +00:00
|
|
|
} from '../../../react/features/remote-video-menu';
|
2018-08-08 18:48:23 +00:00
|
|
|
import {
|
|
|
|
LAYOUTS,
|
2018-11-30 22:13:39 +00:00
|
|
|
getCurrentLayout
|
2018-08-08 18:48:23 +00:00
|
|
|
} from '../../../react/features/video-layout';
|
2017-05-31 15:42:50 +00:00
|
|
|
/* eslint-enable no-unused-vars */
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
2015-12-14 12:26:50 +00:00
|
|
|
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
import SmallVideo from './SmallVideo';
|
|
|
|
import UIUtils from '../util/UIUtil';
|
2015-12-14 12:26:50 +00:00
|
|
|
|
2016-09-16 20:17:00 +00:00
|
|
|
/**
|
|
|
|
* 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) {
|
|
|
|
this.user = user;
|
|
|
|
this.id = user.getId();
|
2015-12-14 12:26:50 +00:00
|
|
|
this.emitter = emitter;
|
2016-09-16 20:17:00 +00:00
|
|
|
this.videoSpanId = `participant_${this.id}`;
|
2016-03-15 20:42:53 +00:00
|
|
|
SmallVideo.call(this, VideoLayout);
|
2017-02-12 06:02:16 +00:00
|
|
|
this._audioStreamElement = null;
|
2017-01-06 01:18:07 +00:00
|
|
|
this._supportsRemoteControl = false;
|
2017-08-14 15:02:58 +00:00
|
|
|
this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP
|
2017-10-03 16:30:42 +00:00
|
|
|
? 'left bottom' : 'top center';
|
2015-06-23 08:00:46 +00:00
|
|
|
this.addRemoteVideoContainer();
|
2017-07-05 18:17:30 +00:00
|
|
|
this.updateIndicators();
|
2019-03-26 16:34:02 +00:00
|
|
|
this.updateDisplayName();
|
2016-10-26 20:45:51 +00:00
|
|
|
this.bindHoverHandler();
|
2015-06-23 08:00:46 +00:00
|
|
|
this.flipX = false;
|
2015-07-15 10:14:34 +00:00
|
|
|
this.isLocal = false;
|
2016-10-28 16:16:40 +00:00
|
|
|
this.popupMenuIsHovered = false;
|
2017-08-17 16:43:22 +00:00
|
|
|
this._isRemoteControlSessionActive = false;
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-09-22 20:57:43 +00:00
|
|
|
/**
|
|
|
|
* The flag is set to <tt>true</tt> after the 'onplay' event has been
|
|
|
|
* triggered on the current video element. It goes back to <tt>false</tt>
|
|
|
|
* when the stream is removed. It is used to determine whether the video
|
|
|
|
* playback has ever started.
|
|
|
|
* @type {boolean}
|
|
|
|
*/
|
|
|
|
this.wasVideoPlayed = false;
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-09-22 21:21:01 +00:00
|
|
|
/**
|
|
|
|
* The flag is set to <tt>true</tt> if remote participant's video gets muted
|
|
|
|
* during his media connection disruption. This is to prevent black video
|
|
|
|
* being render on the thumbnail, because even though once the video has
|
|
|
|
* been played the image usually remains on the video element it seems that
|
|
|
|
* after longer period of the video element being hidden this image can be
|
|
|
|
* lost.
|
|
|
|
* @type {boolean}
|
|
|
|
*/
|
|
|
|
this.mutedWhileDisconnected = false;
|
2017-05-31 15:42:50 +00:00
|
|
|
|
|
|
|
// Bind event handlers so they are only bound once for every instance.
|
|
|
|
// TODO The event handlers should be turned into actions so changes can be
|
|
|
|
// handled through reducers and middleware.
|
|
|
|
this._requestRemoteControlPermissions
|
|
|
|
= this._requestRemoteControlPermissions.bind(this);
|
|
|
|
this._setAudioVolume = this._setAudioVolume.bind(this);
|
|
|
|
this._stopRemoteControl = this._stopRemoteControl.bind(this);
|
2017-11-09 00:17:38 +00:00
|
|
|
|
2018-11-30 22:13:39 +00:00
|
|
|
this.container.onclick = this._onContainerClick;
|
2015-06-23 08:00:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
RemoteVideo.prototype = Object.create(SmallVideo.prototype);
|
|
|
|
RemoteVideo.prototype.constructor = RemoteVideo;
|
|
|
|
|
|
|
|
RemoteVideo.prototype.addRemoteVideoContainer = function() {
|
|
|
|
this.container = RemoteVideo.createContainer(this.videoSpanId);
|
2017-06-30 23:07:37 +00:00
|
|
|
this.$container = $(this.container);
|
2016-08-08 22:03:00 +00:00
|
|
|
|
|
|
|
this.initBrowserSpecificProperties();
|
|
|
|
|
2017-11-14 19:16:03 +00:00
|
|
|
this.updateRemoteVideoMenu();
|
2016-09-15 02:20:54 +00:00
|
|
|
|
2018-04-10 20:03:44 +00:00
|
|
|
this.VideoLayout.resizeThumbnails(true);
|
2016-09-28 21:31:40 +00:00
|
|
|
|
|
|
|
this.addAudioLevelIndicator();
|
2015-06-23 08:00:46 +00:00
|
|
|
|
2017-07-31 23:33:22 +00:00
|
|
|
this.addPresenceLabel();
|
|
|
|
|
2015-06-23 08:00:46 +00:00
|
|
|
return this.container;
|
|
|
|
};
|
|
|
|
|
2016-10-28 16:16:40 +00:00
|
|
|
/**
|
|
|
|
* Checks whether current video is considered hovered. Currently it is hovered
|
|
|
|
* if the mouse is over the video, or if the connection indicator or the popup
|
|
|
|
* menu is shown(hovered).
|
|
|
|
* @private
|
|
|
|
* NOTE: extends SmallVideo's method
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype._isHovered = function() {
|
|
|
|
const isHovered = SmallVideo.prototype._isHovered.call(this)
|
2016-10-28 16:16:40 +00:00
|
|
|
|| this.popupMenuIsHovered;
|
2017-10-12 23:02:29 +00:00
|
|
|
|
|
|
|
|
2016-10-28 16:16:40 +00:00
|
|
|
return isHovered;
|
|
|
|
};
|
|
|
|
|
2016-08-04 20:23:38 +00:00
|
|
|
/**
|
|
|
|
* Generates the popup menu content.
|
|
|
|
*
|
|
|
|
* @returns {Element|*} the constructed element, containing popup menu items
|
|
|
|
* @private
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype._generatePopupContent = function() {
|
2017-08-14 15:02:58 +00:00
|
|
|
if (interfaceConfig.filmStripOnly) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const remoteVideoMenuContainer
|
|
|
|
= this.container.querySelector('.remotevideomenu');
|
|
|
|
|
|
|
|
if (!remoteVideoMenuContainer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-05-31 15:42:50 +00:00
|
|
|
const { controller } = APP.remoteControl;
|
|
|
|
let remoteControlState = null;
|
|
|
|
let onRemoteControlToggle;
|
|
|
|
|
2017-08-17 16:43:22 +00:00
|
|
|
if (this._supportsRemoteControl
|
|
|
|
&& ((!APP.remoteControl.active && !this._isRemoteControlSessionActive)
|
|
|
|
|| APP.remoteControl.controller.activeParticipant === this.id)) {
|
2017-05-31 15:42:50 +00:00
|
|
|
if (controller.getRequestedParticipant() === this.id) {
|
|
|
|
remoteControlState = REMOTE_CONTROL_MENU_STATES.REQUESTING;
|
2017-10-12 23:02:29 +00:00
|
|
|
} else if (controller.isStarted()) {
|
2017-05-31 15:42:50 +00:00
|
|
|
onRemoteControlToggle = this._stopRemoteControl;
|
|
|
|
remoteControlState = REMOTE_CONTROL_MENU_STATES.STARTED;
|
2017-10-12 23:02:29 +00:00
|
|
|
} else {
|
|
|
|
onRemoteControlToggle = this._requestRemoteControlPermissions;
|
|
|
|
remoteControlState = REMOTE_CONTROL_MENU_STATES.NOT_STARTED;
|
2017-01-06 01:18:07 +00:00
|
|
|
}
|
|
|
|
}
|
2016-08-04 20:23:38 +00:00
|
|
|
|
2018-06-27 09:43:13 +00:00
|
|
|
const initialVolumeValue
|
|
|
|
= this._audioStreamElement && this._audioStreamElement.volume;
|
2019-06-14 11:16:08 +00:00
|
|
|
|
|
|
|
// hide volume when in silent mode
|
|
|
|
const onVolumeChange = APP.store.getState()['features/base/config'].startSilent
|
|
|
|
? undefined : this._setAudioVolume;
|
2017-05-31 15:42:50 +00:00
|
|
|
const { isModerator } = APP.conference;
|
|
|
|
const participantID = this.id;
|
2018-08-08 18:48:23 +00:00
|
|
|
|
|
|
|
const currentLayout = getCurrentLayout(APP.store.getState());
|
|
|
|
let remoteMenuPosition;
|
|
|
|
|
|
|
|
if (currentLayout === LAYOUTS.TILE_VIEW) {
|
|
|
|
remoteMenuPosition = 'left top';
|
|
|
|
} else if (currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW) {
|
|
|
|
remoteMenuPosition = 'left bottom';
|
|
|
|
} else {
|
|
|
|
remoteMenuPosition = 'top center';
|
|
|
|
}
|
2017-05-31 15:42:50 +00:00
|
|
|
|
2017-08-14 15:02:58 +00:00
|
|
|
ReactDOM.render(
|
|
|
|
<Provider store = { APP.store }>
|
|
|
|
<I18nextProvider i18n = { i18next }>
|
2017-10-06 16:14:45 +00:00
|
|
|
<AtlasKitThemeProvider mode = 'dark'>
|
|
|
|
<RemoteVideoMenuTriggerButton
|
|
|
|
initialVolumeValue = { initialVolumeValue }
|
|
|
|
isAudioMuted = { this.isAudioMuted }
|
|
|
|
isModerator = { isModerator }
|
2018-08-08 18:48:23 +00:00
|
|
|
menuPosition = { remoteMenuPosition }
|
2017-10-06 16:14:45 +00:00
|
|
|
onMenuDisplay
|
|
|
|
= {this._onRemoteVideoMenuDisplay.bind(this)}
|
|
|
|
onRemoteControlToggle = { onRemoteControlToggle }
|
|
|
|
onVolumeChange = { onVolumeChange }
|
|
|
|
participantID = { participantID }
|
|
|
|
remoteControlState = { remoteControlState } />
|
|
|
|
</AtlasKitThemeProvider>
|
2017-08-14 15:02:58 +00:00
|
|
|
</I18nextProvider>
|
|
|
|
</Provider>,
|
|
|
|
remoteVideoMenuContainer);
|
2016-10-27 13:09:27 +00:00
|
|
|
};
|
2016-08-04 20:23:38 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype._onRemoteVideoMenuDisplay = function() {
|
2017-08-17 16:43:22 +00:00
|
|
|
this.updateRemoteVideoMenu();
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the remote control active status for the remote video.
|
|
|
|
*
|
|
|
|
* @param {boolean} isActive - The new remote control active status.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
RemoteVideo.prototype.setRemoteControlActiveStatus = function(isActive) {
|
|
|
|
this._isRemoteControlSessionActive = isActive;
|
|
|
|
this.updateRemoteVideoMenu();
|
2017-08-14 15:02:58 +00:00
|
|
|
};
|
|
|
|
|
2017-01-06 01:18:07 +00:00
|
|
|
/**
|
|
|
|
* 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) {
|
2017-10-12 23:02:29 +00:00
|
|
|
if (this._supportsRemoteControl === isSupported) {
|
2017-01-06 01:18:07 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._supportsRemoteControl = isSupported;
|
2017-08-17 16:43:22 +00:00
|
|
|
this.updateRemoteVideoMenu();
|
2017-01-06 01:18:07 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Requests permissions for remote control session.
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype._requestRemoteControlPermissions = function() {
|
2017-01-20 20:26:25 +00:00
|
|
|
APP.remoteControl.controller.requestPermissions(
|
|
|
|
this.id, this.VideoLayout.getLargeVideoWrapper()).then(result => {
|
2017-10-12 23:02:29 +00:00
|
|
|
if (result === null) {
|
2017-01-06 01:18:07 +00:00
|
|
|
return;
|
|
|
|
}
|
2017-08-17 16:43:22 +00:00
|
|
|
this.updateRemoteVideoMenu();
|
2017-07-10 03:01:48 +00:00
|
|
|
APP.UI.messageHandler.notify(
|
2017-10-12 23:02:29 +00:00
|
|
|
'dialog.remoteControlTitle',
|
|
|
|
result === false ? 'dialog.remoteControlDeniedMessage'
|
|
|
|
: 'dialog.remoteControlAllowedMessage',
|
|
|
|
{ user: this.user.getDisplayName()
|
|
|
|
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME }
|
2017-01-06 01:18:07 +00:00
|
|
|
);
|
2017-10-12 23:02:29 +00:00
|
|
|
if (result === true) {
|
|
|
|
// the remote control permissions has been granted
|
2017-01-20 20:32:30 +00:00
|
|
|
// pin the controlled participant
|
2018-05-18 19:59:07 +00:00
|
|
|
const pinnedParticipant
|
|
|
|
= getPinnedParticipant(APP.store.getState()) || {};
|
|
|
|
const pinnedId = pinnedParticipant.id;
|
2017-10-12 23:02:29 +00:00
|
|
|
|
|
|
|
if (pinnedId !== this.id) {
|
2018-05-18 19:59:07 +00:00
|
|
|
APP.store.dispatch(pinParticipant(this.id));
|
2017-01-20 20:32:30 +00:00
|
|
|
}
|
2017-01-10 18:51:25 +00:00
|
|
|
}
|
2017-01-06 01:18:07 +00:00
|
|
|
}, error => {
|
|
|
|
logger.error(error);
|
2017-08-17 16:43:22 +00:00
|
|
|
this.updateRemoteVideoMenu();
|
2017-07-10 03:01:48 +00:00
|
|
|
APP.UI.messageHandler.notify(
|
2017-10-12 23:02:29 +00:00
|
|
|
'dialog.remoteControlTitle',
|
|
|
|
'dialog.remoteControlErrorMessage',
|
|
|
|
{ user: this.user.getDisplayName()
|
|
|
|
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME }
|
2017-01-06 01:18:07 +00:00
|
|
|
);
|
|
|
|
});
|
2017-08-17 16:43:22 +00:00
|
|
|
this.updateRemoteVideoMenu();
|
2017-01-06 01:18:07 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stops remote control session.
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype._stopRemoteControl = function() {
|
2017-01-06 01:18:07 +00:00
|
|
|
// send message about stopping
|
|
|
|
APP.remoteControl.controller.stop();
|
2017-08-17 16:43:22 +00:00
|
|
|
this.updateRemoteVideoMenu();
|
2017-01-06 01:18:07 +00:00
|
|
|
};
|
|
|
|
|
2017-02-12 06:02:16 +00:00
|
|
|
/**
|
|
|
|
* Change the remote participant's volume level.
|
|
|
|
*
|
|
|
|
* @param {int} newVal - The value to set the slider to.
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype._setAudioVolume = function(newVal) {
|
2018-06-27 09:43:13 +00:00
|
|
|
if (this._audioStreamElement) {
|
|
|
|
this._audioStreamElement.volume = newVal;
|
2017-02-12 06:02:16 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-08-04 20:23:38 +00:00
|
|
|
/**
|
|
|
|
* Updates the remote video menu.
|
|
|
|
*
|
|
|
|
* @param isMuted the new muted state to update to
|
|
|
|
*/
|
2017-11-02 19:07:31 +00:00
|
|
|
RemoteVideo.prototype.updateRemoteVideoMenu = function(isMuted) {
|
|
|
|
|
|
|
|
if (typeof isMuted !== 'undefined') {
|
|
|
|
this.isAudioMuted = isMuted;
|
|
|
|
}
|
2016-08-04 20:23:38 +00:00
|
|
|
|
2017-08-14 15:02:58 +00:00
|
|
|
this._generatePopupContent();
|
2016-08-04 20:23:38 +00:00
|
|
|
};
|
|
|
|
|
2016-09-22 21:21:01 +00:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
2017-05-25 13:48:06 +00:00
|
|
|
* @override
|
2016-09-22 21:21:01 +00:00
|
|
|
*/
|
2017-05-25 13:48:06 +00:00
|
|
|
RemoteVideo.prototype.setVideoMutedView = function(isMuted) {
|
|
|
|
SmallVideo.prototype.setVideoMutedView.call(this, isMuted);
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-09-22 21:21:01 +00:00
|
|
|
// Update 'mutedWhileDisconnected' flag
|
2017-05-25 13:48:06 +00:00
|
|
|
this._figureOutMutedWhileDisconnected();
|
2016-09-28 16:29:47 +00:00
|
|
|
};
|
2016-09-22 21:21:01 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Figures out the value of {@link #mutedWhileDisconnected} flag by taking into
|
|
|
|
* account remote participant's network connectivity and video muted status.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
2017-05-25 13:48:06 +00:00
|
|
|
RemoteVideo.prototype._figureOutMutedWhileDisconnected = function() {
|
|
|
|
const isActive = this.isConnectionActive();
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-05-25 13:48:06 +00:00
|
|
|
if (!isActive && this.isVideoMuted) {
|
|
|
|
this.mutedWhileDisconnected = true;
|
|
|
|
} else if (isActive && !this.isVideoMuted) {
|
|
|
|
this.mutedWhileDisconnected = false;
|
|
|
|
}
|
2016-09-28 16:29:47 +00:00
|
|
|
};
|
2016-09-22 21:21:01 +00:00
|
|
|
|
2015-06-23 08:00:46 +00:00
|
|
|
/**
|
|
|
|
* Removes the remote stream element corresponding to the given stream and
|
|
|
|
* parent container.
|
|
|
|
*
|
2016-01-29 00:33:27 +00:00
|
|
|
* @param stream the MediaStream
|
2015-06-23 08:00:46 +00:00
|
|
|
* @param isVideo <tt>true</tt> if given <tt>stream</tt> is a video one.
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype.removeRemoteStreamElement = function(stream) {
|
|
|
|
if (!this.container) {
|
2015-06-23 08:00:46 +00:00
|
|
|
return false;
|
2017-10-12 23:02:29 +00:00
|
|
|
}
|
2015-06-23 08:00:46 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
const isVideo = stream.isVideoTrack();
|
|
|
|
|
|
|
|
const elementID = SmallVideo.getStreamElementID(stream);
|
|
|
|
const select = $(`#${elementID}`);
|
2016-02-23 22:47:55 +00:00
|
|
|
|
2015-06-23 08:00:46 +00:00
|
|
|
select.remove();
|
|
|
|
|
2016-09-22 20:57:43 +00:00
|
|
|
if (isVideo) {
|
|
|
|
this.wasVideoPlayed = false;
|
|
|
|
}
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
logger.info(`${isVideo ? 'Video' : 'Audio'
|
|
|
|
} removed ${this.id}`, select);
|
2015-07-30 08:23:21 +00:00
|
|
|
|
2019-05-28 20:30:27 +00:00
|
|
|
|
|
|
|
if (stream === this.videoStream) {
|
|
|
|
this.videoStream = null;
|
|
|
|
}
|
|
|
|
|
2018-05-21 21:54:16 +00:00
|
|
|
this.updateView();
|
2015-06-23 08:00:46 +00:00
|
|
|
};
|
|
|
|
|
2016-09-16 20:51:19 +00:00
|
|
|
/**
|
|
|
|
* Checks whether the remote user associated with this <tt>RemoteVideo</tt>
|
|
|
|
* has connectivity issues.
|
|
|
|
*
|
|
|
|
* @return {boolean} <tt>true</tt> if the user's connection is fine or
|
|
|
|
* <tt>false</tt> otherwise.
|
|
|
|
*/
|
|
|
|
RemoteVideo.prototype.isConnectionActive = function() {
|
2017-04-03 16:53:04 +00:00
|
|
|
return this.user.getConnectionStatus()
|
2017-10-10 23:31:40 +00:00
|
|
|
=== JitsiParticipantConnectionStatus.ACTIVE;
|
2016-09-16 20:51:19 +00:00
|
|
|
};
|
|
|
|
|
2016-09-19 20:59:56 +00:00
|
|
|
/**
|
|
|
|
* The remote video is considered "playable" once the stream has started
|
|
|
|
* according to the {@link #hasVideoStarted} result.
|
2017-05-30 20:05:07 +00:00
|
|
|
* It will be allowed to display video also in
|
2017-10-12 23:02:29 +00:00
|
|
|
* {@link JitsiParticipantConnectionStatus.INTERRUPTED} if the video was ever
|
|
|
|
* played and was not muted while not in ACTIVE state. This basically means
|
|
|
|
* that there is stalled video image cached that could be displayed. It's used
|
|
|
|
* to show "grey video image" in user's thumbnail when there are connectivity
|
|
|
|
* issues.
|
2016-09-19 20:59:56 +00:00
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
* @override
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype.isVideoPlayable = function() {
|
2017-05-30 20:05:07 +00:00
|
|
|
const connectionState
|
|
|
|
= APP.conference.getParticipantConnectionStatus(this.id);
|
|
|
|
|
2016-09-19 20:59:56 +00:00
|
|
|
return SmallVideo.prototype.isVideoPlayable.call(this)
|
2017-05-30 20:05:07 +00:00
|
|
|
&& this.hasVideoStarted()
|
2017-10-10 23:31:40 +00:00
|
|
|
&& (connectionState === JitsiParticipantConnectionStatus.ACTIVE
|
|
|
|
|| (connectionState === JitsiParticipantConnectionStatus.INTERRUPTED
|
2017-05-30 20:05:07 +00:00
|
|
|
&& !this.mutedWhileDisconnected));
|
2016-09-19 20:59:56 +00:00
|
|
|
};
|
|
|
|
|
2016-09-16 20:51:19 +00:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype.updateView = function() {
|
2017-06-30 23:07:37 +00:00
|
|
|
this.$container.toggleClass('audio-only', APP.conference.isAudioOnly());
|
2016-09-22 21:21:01 +00:00
|
|
|
|
2017-03-21 17:14:13 +00:00
|
|
|
this.updateConnectionStatusIndicator();
|
2016-09-22 21:21:01 +00:00
|
|
|
|
|
|
|
// This must be called after 'updateConnectionStatusIndicator' because it
|
|
|
|
// affects the display mode by modifying 'mutedWhileDisconnected' flag
|
|
|
|
SmallVideo.prototype.updateView.call(this);
|
2016-09-16 20:51:19 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the UI to reflect user's connectivity status.
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype.updateConnectionStatusIndicator = function() {
|
2017-05-25 13:48:06 +00:00
|
|
|
const connectionStatus = this.user.getConnectionStatus();
|
2017-03-21 17:14:13 +00:00
|
|
|
|
2017-05-25 13:48:06 +00:00
|
|
|
logger.debug(`${this.id} thumbnail connection status: ${connectionStatus}`);
|
2016-09-16 20:51:19 +00:00
|
|
|
|
2017-05-25 13:48:06 +00:00
|
|
|
// FIXME rename 'mutedWhileDisconnected' to 'mutedWhileNotRendering'
|
2016-09-22 21:21:01 +00:00
|
|
|
// Update 'mutedWhileDisconnected' flag
|
2017-05-25 13:48:06 +00:00
|
|
|
this._figureOutMutedWhileDisconnected();
|
2017-06-19 20:20:13 +00:00
|
|
|
this.updateConnectionStatus(connectionStatus);
|
2016-09-19 19:18:52 +00:00
|
|
|
|
2017-05-25 13:48:06 +00:00
|
|
|
const isInterrupted
|
2017-10-10 23:31:40 +00:00
|
|
|
= connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED;
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-09-19 19:18:52 +00:00
|
|
|
// Toggle thumbnail video problem filter
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-09-19 19:18:52 +00:00
|
|
|
this.selectVideoElement().toggleClass(
|
2017-10-12 23:02:29 +00:00
|
|
|
'videoThumbnailProblemFilter', isInterrupted);
|
2016-09-19 19:18:52 +00:00
|
|
|
this.$avatar().toggleClass(
|
2017-10-12 23:02:29 +00:00
|
|
|
'videoThumbnailProblemFilter', isInterrupted);
|
2015-06-23 08:00:46 +00:00
|
|
|
};
|
|
|
|
|
2015-07-29 16:41:22 +00:00
|
|
|
/**
|
|
|
|
* Removes RemoteVideo from the page.
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype.remove = function() {
|
2019-01-01 21:19:34 +00:00
|
|
|
SmallVideo.prototype.remove.call(this);
|
2017-06-29 18:21:03 +00:00
|
|
|
|
2017-07-31 23:33:22 +00:00
|
|
|
this.removePresenceLabel();
|
2017-08-14 15:02:58 +00:00
|
|
|
this.removeRemoteVideoMenu();
|
2015-07-29 16:41:22 +00:00
|
|
|
};
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype.waitForPlayback = function(streamElement, stream) {
|
|
|
|
|
|
|
|
const webRtcStream = stream.getOriginalStream();
|
|
|
|
const isVideo = stream.isVideoTrack();
|
2015-07-15 12:01:36 +00:00
|
|
|
|
2015-11-06 21:59:38 +00:00
|
|
|
if (!isVideo || webRtcStream.id === 'mixedmslabel') {
|
2015-07-15 12:01:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
const self = this;
|
2015-07-15 12:01:36 +00:00
|
|
|
|
2016-11-08 02:47:43 +00:00
|
|
|
// Triggers when video playback starts
|
2017-10-12 23:02:29 +00:00
|
|
|
const onPlayingHandler = function() {
|
2016-09-22 20:57:43 +00:00
|
|
|
self.wasVideoPlayed = true;
|
2016-11-08 02:47:43 +00:00
|
|
|
self.VideoLayout.remoteVideoActive(streamElement, self.id);
|
2016-02-01 21:00:51 +00:00
|
|
|
streamElement.onplaying = null;
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-09-19 19:18:52 +00:00
|
|
|
// Refresh to show the video
|
|
|
|
self.updateView();
|
2015-07-15 12:01:36 +00:00
|
|
|
};
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-02-01 21:00:51 +00:00
|
|
|
streamElement.onplaying = onPlayingHandler;
|
2015-07-15 12:01:36 +00:00
|
|
|
};
|
|
|
|
|
2016-02-01 22:08:15 +00:00
|
|
|
/**
|
2016-09-22 20:57:43 +00:00
|
|
|
* Checks whether the video stream has started for this RemoteVideo instance.
|
2016-02-01 22:08:15 +00:00
|
|
|
*
|
2016-09-22 20:57:43 +00:00
|
|
|
* @returns {boolean} true if this RemoteVideo has a video stream for which
|
|
|
|
* the playback has been started.
|
2016-02-01 22:08:15 +00:00
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype.hasVideoStarted = function() {
|
2016-09-22 20:57:43 +00:00
|
|
|
return this.wasVideoPlayed;
|
2016-02-01 22:08:15 +00:00
|
|
|
};
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype.addRemoteStreamElement = function(stream) {
|
2015-12-14 12:26:50 +00:00
|
|
|
if (!this.container) {
|
2015-06-23 08:00:46 +00:00
|
|
|
return;
|
2015-12-14 12:26:50 +00:00
|
|
|
}
|
2015-06-23 08:00:46 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
const isVideo = stream.isVideoTrack();
|
|
|
|
|
2016-01-29 00:33:27 +00:00
|
|
|
isVideo ? this.videoStream = stream : this.audioStream = stream;
|
2015-06-23 08:00:46 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
if (isVideo) {
|
2016-07-21 03:13:26 +00:00
|
|
|
this.setVideoType(stream.videoType);
|
2017-10-12 23:02:29 +00:00
|
|
|
}
|
2016-07-21 03:13:26 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
if (!stream.getOriginalStream()) {
|
2016-01-22 22:37:33 +00:00
|
|
|
return;
|
2017-10-12 23:02:29 +00:00
|
|
|
}
|
2016-01-22 22:37:33 +00:00
|
|
|
|
2018-06-27 09:43:13 +00:00
|
|
|
const streamElement = SmallVideo.createStreamElement(stream);
|
2016-01-22 22:37:33 +00:00
|
|
|
|
|
|
|
// Put new stream element always in front
|
|
|
|
UIUtils.prependChild(this.container, streamElement);
|
|
|
|
|
2016-02-02 21:50:02 +00:00
|
|
|
$(streamElement).hide();
|
2016-01-22 22:37:33 +00:00
|
|
|
|
2016-02-10 15:26:16 +00:00
|
|
|
// If the container is currently visible
|
|
|
|
// we attach the stream to the element.
|
2016-01-22 22:37:33 +00:00
|
|
|
if (!isVideo || (this.container.offsetParent !== null && isVideo)) {
|
2016-02-01 21:00:51 +00:00
|
|
|
this.waitForPlayback(streamElement, stream);
|
2018-06-27 09:43:13 +00:00
|
|
|
stream.attach(streamElement);
|
2016-01-22 22:37:33 +00:00
|
|
|
}
|
|
|
|
|
2017-02-12 06:02:16 +00:00
|
|
|
if (!isVideo) {
|
|
|
|
this._audioStreamElement = streamElement;
|
2017-08-17 16:43:22 +00:00
|
|
|
|
|
|
|
// If the remote video menu was created before the audio stream was
|
|
|
|
// attached we need to update the menu in order to show the volume
|
|
|
|
// slider.
|
|
|
|
this.updateRemoteVideoMenu();
|
2017-02-12 06:02:16 +00:00
|
|
|
}
|
2016-10-31 16:12:28 +00:00
|
|
|
};
|
2015-06-23 08:00:46 +00:00
|
|
|
|
|
|
|
/**
|
2019-03-26 16:34:02 +00:00
|
|
|
* Triggers re-rendering of the display name using current instance state.
|
2016-10-20 19:28:10 +00:00
|
|
|
*
|
2019-03-26 16:34:02 +00:00
|
|
|
* @returns {void}
|
2015-06-23 08:00:46 +00:00
|
|
|
*/
|
2019-03-26 16:34:02 +00:00
|
|
|
RemoteVideo.prototype.updateDisplayName = function() {
|
2015-06-23 08:00:46 +00:00
|
|
|
if (!this.container) {
|
2017-10-12 23:02:29 +00:00
|
|
|
logger.warn(`Unable to set displayName - ${this.videoSpanId
|
|
|
|
} does not exist`);
|
|
|
|
|
2015-06-23 08:00:46 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-26 16:34:02 +00:00
|
|
|
this._renderDisplayName({
|
2017-06-29 03:35:43 +00:00
|
|
|
elementID: `${this.videoSpanId}_name`,
|
|
|
|
participantID: this.id
|
|
|
|
});
|
2015-07-28 21:52:32 +00:00
|
|
|
};
|
2015-06-23 08:00:46 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes remote video menu element from video element identified by
|
|
|
|
* given <tt>videoElementId</tt>.
|
|
|
|
*
|
|
|
|
* @param videoElementId the id of local or remote video element.
|
|
|
|
*/
|
|
|
|
RemoteVideo.prototype.removeRemoteVideoMenu = function() {
|
2017-10-12 23:02:29 +00:00
|
|
|
const menuSpan = this.$container.find('.remotevideomenu');
|
2017-08-14 15:02:58 +00:00
|
|
|
|
2015-06-23 08:00:46 +00:00
|
|
|
if (menuSpan.length) {
|
2017-08-14 15:02:58 +00:00
|
|
|
ReactDOM.unmountComponentAtNode(menuSpan.get(0));
|
2015-06-23 08:00:46 +00:00
|
|
|
menuSpan.remove();
|
|
|
|
}
|
2015-07-20 17:32:04 +00:00
|
|
|
};
|
|
|
|
|
2017-07-31 23:33:22 +00:00
|
|
|
/**
|
|
|
|
* Mounts the {@code PresenceLabel} for displaying the participant's current
|
|
|
|
* presence status.
|
|
|
|
*
|
|
|
|
* @return {void}
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype.addPresenceLabel = function() {
|
2017-07-31 23:33:22 +00:00
|
|
|
const presenceLabelContainer
|
|
|
|
= this.container.querySelector('.presence-label-container');
|
|
|
|
|
|
|
|
if (presenceLabelContainer) {
|
|
|
|
ReactDOM.render(
|
|
|
|
<Provider store = { APP.store }>
|
2018-05-16 15:03:10 +00:00
|
|
|
<I18nextProvider i18n = { i18next }>
|
2018-06-26 22:56:22 +00:00
|
|
|
<PresenceLabel
|
|
|
|
participantID = { this.id }
|
2018-07-09 22:18:09 +00:00
|
|
|
className = 'presence-label' />
|
2018-05-16 15:03:10 +00:00
|
|
|
</I18nextProvider>
|
2017-07-31 23:33:22 +00:00
|
|
|
</Provider>,
|
|
|
|
presenceLabelContainer);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unmounts the {@code PresenceLabel} component.
|
|
|
|
*
|
|
|
|
* @return {void}
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.prototype.removePresenceLabel = function() {
|
2017-07-31 23:33:22 +00:00
|
|
|
const presenceLabelContainer
|
|
|
|
= this.container.querySelector('.presence-label-container');
|
|
|
|
|
|
|
|
if (presenceLabelContainer) {
|
|
|
|
ReactDOM.unmountComponentAtNode(presenceLabelContainer);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
RemoteVideo.createContainer = function(spanId) {
|
2017-06-30 22:26:37 +00:00
|
|
|
const container = document.createElement('span');
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2015-06-23 08:00:46 +00:00
|
|
|
container.id = spanId;
|
2016-11-04 20:24:05 +00:00
|
|
|
container.className = 'videocontainer';
|
2016-09-15 02:20:54 +00:00
|
|
|
|
2017-06-30 22:26:37 +00:00
|
|
|
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>`;
|
2017-08-14 15:02:58 +00:00
|
|
|
|
2018-07-26 17:51:15 +00:00
|
|
|
const remoteVideosContainer
|
|
|
|
= document.getElementById('filmstripRemoteVideosContainer');
|
|
|
|
const localVideoContainer
|
|
|
|
= document.getElementById('localVideoTileViewContainer');
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2018-07-26 17:51:15 +00:00
|
|
|
remoteVideosContainer.insertBefore(container, localVideoContainer);
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2018-07-26 17:51:15 +00:00
|
|
|
return container;
|
2015-06-23 08:00:46 +00:00
|
|
|
};
|
|
|
|
|
2015-12-14 12:26:50 +00:00
|
|
|
export default RemoteVideo;
|