2017-10-10 23:31:40 +00:00
|
|
|
/* global $, config, interfaceConfig, APP */
|
2017-07-14 19:22:27 +00:00
|
|
|
|
|
|
|
/* eslint-disable no-unused-vars */
|
|
|
|
import React, { Component } from 'react';
|
|
|
|
import ReactDOM from 'react-dom';
|
|
|
|
import { Provider } from 'react-redux';
|
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
|
2017-07-14 19:22:27 +00:00
|
|
|
import { VideoTrack } from '../../../react/features/base/media';
|
2017-12-19 23:11:54 +00:00
|
|
|
import {
|
|
|
|
getAvatarURLByParticipantId
|
|
|
|
} from '../../../react/features/base/participants';
|
2018-04-12 19:58:20 +00:00
|
|
|
import { updateSettings } from '../../../react/features/base/settings';
|
2017-07-14 19:22:27 +00:00
|
|
|
/* eslint-enable no-unused-vars */
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
2016-11-11 15:00:54 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
import UIEvents from '../../../service/UI/UIEvents';
|
|
|
|
import SmallVideo from './SmallVideo';
|
2015-12-14 12:26:50 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2015-12-01 12:53:01 +00:00
|
|
|
function LocalVideo(VideoLayout, emitter) {
|
2017-10-12 23:02:29 +00:00
|
|
|
this.videoSpanId = 'localVideoContainer';
|
2017-06-30 17:40:55 +00:00
|
|
|
|
|
|
|
this.container = this.createContainer();
|
2017-06-30 23:07:37 +00:00
|
|
|
this.$container = $(this.container);
|
2017-11-02 16:22:33 +00:00
|
|
|
$('#filmstripLocalVideoThumbnail').append(this.container);
|
2017-06-30 17:40:55 +00:00
|
|
|
|
2016-05-07 01:50:37 +00:00
|
|
|
this.localVideoId = null;
|
2016-10-26 20:45:51 +00:00
|
|
|
this.bindHoverHandler();
|
2017-10-12 23:02:29 +00:00
|
|
|
if (config.enableLocalVideoFlip) {
|
2016-05-09 17:39:42 +00:00
|
|
|
this._buildContextMenu();
|
2017-10-12 23:02:29 +00:00
|
|
|
}
|
2015-07-15 10:14:34 +00:00
|
|
|
this.isLocal = true;
|
2015-12-01 12:53:01 +00:00
|
|
|
this.emitter = emitter;
|
2017-08-14 15:02:58 +00:00
|
|
|
this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP
|
2017-10-03 16:30:42 +00:00
|
|
|
? 'left top' : 'top center';
|
2017-08-14 15:02:58 +00:00
|
|
|
|
2016-02-09 10:19:43 +00:00
|
|
|
Object.defineProperty(this, 'id', {
|
2017-10-12 23:02:29 +00:00
|
|
|
get() {
|
2016-07-08 01:44:04 +00:00
|
|
|
return APP.conference.getMyUserId();
|
2016-02-09 10:19:43 +00:00
|
|
|
}
|
|
|
|
});
|
2016-08-08 22:03:00 +00:00
|
|
|
this.initBrowserSpecificProperties();
|
|
|
|
|
2016-03-15 20:42:53 +00:00
|
|
|
SmallVideo.call(this, VideoLayout);
|
2016-08-31 19:18:09 +00:00
|
|
|
|
|
|
|
// Set default display name.
|
|
|
|
this.setDisplayName();
|
|
|
|
|
2017-12-19 23:11:54 +00:00
|
|
|
// Initialize the avatar display with an avatar url selected from the redux
|
|
|
|
// state. Redux stores the local user with a hardcoded participant id of
|
|
|
|
// 'local' if no id has been assigned yet.
|
|
|
|
this.avatarChanged(
|
|
|
|
getAvatarURLByParticipantId(APP.store.getState(), this.id));
|
|
|
|
|
2016-09-28 21:31:40 +00:00
|
|
|
this.addAudioLevelIndicator();
|
2017-07-05 18:17:30 +00:00
|
|
|
this.updateIndicators();
|
2017-11-09 00:17:38 +00:00
|
|
|
|
|
|
|
this.container.onclick = this._onContainerClick.bind(this);
|
2015-06-23 08:00:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
LocalVideo.prototype = Object.create(SmallVideo.prototype);
|
|
|
|
LocalVideo.prototype.constructor = LocalVideo;
|
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
LocalVideo.prototype.createContainer = function() {
|
2017-06-30 17:40:55 +00:00
|
|
|
const containerSpan = document.createElement('span');
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-06-30 17:40:55 +00:00
|
|
|
containerSpan.classList.add('videocontainer');
|
|
|
|
containerSpan.id = this.videoSpanId;
|
|
|
|
|
|
|
|
containerSpan.innerHTML = `
|
|
|
|
<div class = 'videocontainer__background'></div>
|
|
|
|
<span id = 'localVideoWrapper'></span>
|
|
|
|
<div class = 'videocontainer__toolbar'></div>
|
|
|
|
<div class = 'videocontainer__toptoolbar'></div>
|
|
|
|
<div class = 'videocontainer__hoverOverlay'></div>
|
|
|
|
<div class = 'displayNameContainer'></div>
|
|
|
|
<div class = 'avatar-container'></div>`;
|
|
|
|
|
|
|
|
return containerSpan;
|
|
|
|
};
|
|
|
|
|
2015-06-23 08:00:46 +00:00
|
|
|
/**
|
|
|
|
* Sets the display name for the given video span id.
|
|
|
|
*/
|
2016-10-03 16:12:04 +00:00
|
|
|
LocalVideo.prototype.setDisplayName = function(displayName) {
|
2015-06-23 08:00:46 +00:00
|
|
|
if (!this.container) {
|
2016-11-11 15:00:54 +00:00
|
|
|
logger.warn(
|
2017-10-12 23:02:29 +00:00
|
|
|
`Unable to set displayName - ${this.videoSpanId
|
|
|
|
} does not exist`);
|
|
|
|
|
2015-06-23 08:00:46 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-29 03:35:43 +00:00
|
|
|
this.updateDisplayName({
|
2017-12-13 23:37:27 +00:00
|
|
|
allowEditing: APP.store.getState()['features/base/jwt'].isGuest,
|
2017-10-12 23:02:29 +00:00
|
|
|
displayName,
|
2017-06-29 03:35:43 +00:00
|
|
|
displayNameSuffix: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME,
|
|
|
|
elementID: 'localDisplayName',
|
|
|
|
participantID: this.id
|
|
|
|
});
|
2015-07-28 21:52:32 +00:00
|
|
|
};
|
2015-06-23 08:00:46 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
LocalVideo.prototype.changeVideo = function(stream) {
|
2016-01-29 00:33:27 +00:00
|
|
|
this.videoStream = stream;
|
2015-06-23 08:00:46 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
this.localVideoId = `localVideo_${stream.getId()}`;
|
2015-06-23 08:00:46 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
const localVideoContainer = document.getElementById('localVideoWrapper');
|
2015-06-23 08:00:46 +00:00
|
|
|
|
2017-07-14 19:22:27 +00:00
|
|
|
ReactDOM.render(
|
|
|
|
<Provider store = { APP.store }>
|
|
|
|
<VideoTrack
|
|
|
|
id = { this.localVideoId }
|
|
|
|
videoTrack = {{ jitsiTrack: stream }} />
|
|
|
|
</Provider>,
|
|
|
|
localVideoContainer
|
|
|
|
);
|
2015-06-23 08:00:46 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
// eslint-disable-next-line eqeqeq
|
|
|
|
const isVideo = stream.videoType != 'desktop';
|
2018-04-12 19:58:20 +00:00
|
|
|
const settings = APP.store.getState()['features/base/settings'];
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-05-07 01:50:37 +00:00
|
|
|
this._enableDisableContextMenu(isVideo);
|
2018-04-12 19:58:20 +00:00
|
|
|
this.setFlipX(isVideo ? settings.localFlipX : false);
|
2017-10-12 23:02:29 +00:00
|
|
|
|
|
|
|
const endedHandler = () => {
|
2015-06-23 08:00:46 +00:00
|
|
|
|
2017-07-14 19:22:27 +00:00
|
|
|
// Only remove if there is no video and not a transition state.
|
|
|
|
// Previous non-react logic created a new video element with each track
|
|
|
|
// removal whereas react reuses the video component so it could be the
|
|
|
|
// stream ended but a new one is being used.
|
|
|
|
if (this.videoStream.isEnded()) {
|
|
|
|
ReactDOM.unmountComponentAtNode(localVideoContainer);
|
|
|
|
}
|
|
|
|
|
2016-03-23 22:45:27 +00:00
|
|
|
// when removing only the video element and we are on stage
|
|
|
|
// update the stage
|
2017-06-15 15:01:32 +00:00
|
|
|
if (this.isCurrentlyOnLargeVideo()) {
|
2017-07-10 10:06:48 +00:00
|
|
|
this.VideoLayout.updateLargeVideo(this.id);
|
2017-06-15 15:01:32 +00:00
|
|
|
}
|
2017-10-10 23:31:40 +00:00
|
|
|
stream.off(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
|
2016-01-06 22:39:13 +00:00
|
|
|
};
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2017-10-10 23:31:40 +00:00
|
|
|
stream.on(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
|
2015-07-20 17:32:04 +00:00
|
|
|
};
|
2015-06-23 08:00:46 +00:00
|
|
|
|
2016-05-01 18:35:18 +00:00
|
|
|
/**
|
|
|
|
* Shows or hides the local video container.
|
|
|
|
* @param {boolean} true to make the local video container visible, false
|
|
|
|
* otherwise
|
|
|
|
*/
|
|
|
|
LocalVideo.prototype.setVisible = function(visible) {
|
|
|
|
|
|
|
|
// We toggle the hidden class as an indication to other interested parties
|
|
|
|
// that this container has been hidden on purpose.
|
2017-10-12 23:02:29 +00:00
|
|
|
this.$container.toggleClass('hidden');
|
2016-05-01 18:35:18 +00:00
|
|
|
|
|
|
|
// We still show/hide it as we need to overwrite the style property if we
|
|
|
|
// want our action to take effect. Toggling the display property through
|
|
|
|
// the above css class didn't succeed in overwriting the style.
|
|
|
|
if (visible) {
|
2017-06-30 23:07:37 +00:00
|
|
|
this.$container.show();
|
2017-10-12 23:02:29 +00:00
|
|
|
} else {
|
2017-06-30 23:07:37 +00:00
|
|
|
this.$container.hide();
|
2016-05-01 18:35:18 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-05-07 01:50:37 +00:00
|
|
|
/**
|
|
|
|
* Sets the flipX state of the video.
|
|
|
|
* @param val {boolean} true for flipped otherwise false;
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
LocalVideo.prototype.setFlipX = function(val) {
|
2016-05-07 01:50:37 +00:00
|
|
|
this.emitter.emit(UIEvents.LOCAL_FLIPX_CHANGED, val);
|
2017-10-12 23:02:29 +00:00
|
|
|
if (!this.localVideoId) {
|
2016-05-07 01:50:37 +00:00
|
|
|
return;
|
2017-10-12 23:02:29 +00:00
|
|
|
}
|
|
|
|
if (val) {
|
|
|
|
this.selectVideoElement().addClass('flipVideoX');
|
2016-05-07 01:50:37 +00:00
|
|
|
} else {
|
2017-10-12 23:02:29 +00:00
|
|
|
this.selectVideoElement().removeClass('flipVideoX');
|
2016-05-07 01:50:37 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Builds the context menu for the local video.
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
LocalVideo.prototype._buildContextMenu = function() {
|
2016-05-07 01:50:37 +00:00
|
|
|
$.contextMenu({
|
2017-10-12 23:02:29 +00:00
|
|
|
selector: `#${this.videoSpanId}`,
|
2016-05-07 01:50:37 +00:00
|
|
|
zIndex: 10000,
|
|
|
|
items: {
|
|
|
|
flip: {
|
2017-10-12 23:02:29 +00:00
|
|
|
name: 'Flip',
|
2016-05-07 01:50:37 +00:00
|
|
|
callback: () => {
|
2018-04-12 19:58:20 +00:00
|
|
|
const { store } = APP;
|
|
|
|
const val = !store.getState()['features/base/settings']
|
|
|
|
.localFlipX;
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2016-05-07 01:50:37 +00:00
|
|
|
this.setFlipX(val);
|
2018-04-12 19:58:20 +00:00
|
|
|
store.dispatch(updateSettings({
|
|
|
|
localFlipX: val
|
|
|
|
}));
|
2016-05-07 01:50:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
events: {
|
2017-10-12 23:02:29 +00:00
|
|
|
show(options) {
|
|
|
|
options.items.flip.name
|
|
|
|
= APP.translation.generateTranslationHTML(
|
|
|
|
'videothumbnail.flip');
|
2016-05-07 01:50:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enables or disables the context menu for the local video.
|
|
|
|
* @param enable {boolean} true for enable, false for disable
|
|
|
|
*/
|
2017-10-12 23:02:29 +00:00
|
|
|
LocalVideo.prototype._enableDisableContextMenu = function(enable) {
|
|
|
|
if (this.$container.contextMenu) {
|
2017-06-30 23:07:37 +00:00
|
|
|
this.$container.contextMenu(enable);
|
2017-10-12 23:02:29 +00:00
|
|
|
}
|
2016-05-07 01:50:37 +00:00
|
|
|
};
|
|
|
|
|
2017-11-09 00:17:38 +00:00
|
|
|
/**
|
|
|
|
* Callback invoked when the thumbnail is clicked. Will directly call
|
|
|
|
* VideoLayout to handle thumbnail click if certain elements have not been
|
|
|
|
* clicked.
|
|
|
|
*
|
|
|
|
* @param {MouseEvent} event - The click event to intercept.
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
LocalVideo.prototype._onContainerClick = function(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
|
|
|
|
// point will prevent DisplayName from getting click events. This workaround
|
|
|
|
// should be removeable once LocalVideo is a React Component because then
|
|
|
|
// the components share the same eventing system.
|
|
|
|
const $source = $(event.target || event.srcElement);
|
|
|
|
const { classList } = event.target;
|
|
|
|
|
|
|
|
const clickedOnDisplayName
|
|
|
|
= $source.parents('.displayNameContainer').length > 0;
|
|
|
|
const clickedOnPopover = $source.parents('.popover').length > 0
|
|
|
|
|| classList.contains('popover');
|
|
|
|
|
|
|
|
const ignoreClick = clickedOnDisplayName || clickedOnPopover;
|
|
|
|
|
|
|
|
// FIXME: with Temasys plugin event arg is not an event, but the clicked
|
|
|
|
// object itself, so we have to skip this call
|
|
|
|
if (event.stopPropagation && !ignoreClick) {
|
|
|
|
event.stopPropagation();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ignoreClick) {
|
|
|
|
this.VideoLayout.handleVideoThumbClicked(this.id);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-12-14 12:26:50 +00:00
|
|
|
export default LocalVideo;
|