2020-11-09 20:59:13 +00:00
|
|
|
/* global $, config, APP */
|
2017-07-14 19:22:27 +00:00
|
|
|
|
|
|
|
/* eslint-disable no-unused-vars */
|
|
|
|
import React, { Component } from 'react';
|
|
|
|
import ReactDOM from 'react-dom';
|
2020-11-09 20:59:13 +00:00
|
|
|
import { I18nextProvider } from 'react-i18next';
|
2017-07-14 19:22:27 +00:00
|
|
|
import { Provider } from 'react-redux';
|
|
|
|
|
2020-11-09 20:59:13 +00:00
|
|
|
import { i18next } from '../../../react/features/base/i18n';
|
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';
|
2018-04-12 19:58:20 +00:00
|
|
|
import { updateSettings } from '../../../react/features/base/settings';
|
fix(tile-view): prevent local participant being selected on pin exit
On tile view enter/exit, local video is moved in the DOM (an effect
of not being reactified and moving being easier) and play is called
on its video element. The race condition setup is such: in tile
view with other participants and local video is on large (not
visible in the UI but visible in the app state and pip popout).
The race is such: pin a remote video, large video update is queued,
tile view is exited, local video is moved, play is called,,
onVideoPlaying callback executed, middleware fires mute update,
which checks if local is on large (it is), previous large video
update is cleared, and local is placed on large.
The fix is ensuring the redux representation of local video is
passed in, which holds the boolean videoStarted, which prevents
the onVideoPlaying callback from firing on subsequent plays.
2018-11-27 22:13:47 +00:00
|
|
|
import { getLocalVideoTrack } from '../../../react/features/base/tracks';
|
2020-11-09 20:59:13 +00:00
|
|
|
import Thumbnail from '../../../react/features/filmstrip/components/web/Thumbnail';
|
2018-08-08 18:48:23 +00:00
|
|
|
import { shouldDisplayTileView } from '../../../react/features/video-layout';
|
2017-07-14 19:22:27 +00:00
|
|
|
/* eslint-enable no-unused-vars */
|
2017-10-12 23:02:29 +00:00
|
|
|
import UIEvents from '../../../service/UI/UIEvents';
|
2020-05-20 10:57:03 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
import SmallVideo from './SmallVideo';
|
2015-12-14 12:26:50 +00:00
|
|
|
|
2017-10-12 23:02:29 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2019-12-16 14:15:02 +00:00
|
|
|
export default class LocalVideo extends SmallVideo {
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {*} emitter
|
|
|
|
* @param {*} streamEndedCallback
|
|
|
|
*/
|
2020-11-09 20:59:13 +00:00
|
|
|
constructor(emitter, streamEndedCallback) {
|
|
|
|
super();
|
2019-12-16 14:15:02 +00:00
|
|
|
this.videoSpanId = 'localVideoContainer';
|
|
|
|
this.streamEndedCallback = streamEndedCallback;
|
|
|
|
this.container = this.createContainer();
|
|
|
|
this.$container = $(this.container);
|
2020-01-24 16:28:47 +00:00
|
|
|
this.isLocal = true;
|
|
|
|
this._setThumbnailSize();
|
2019-12-16 14:15:02 +00:00
|
|
|
this.updateDOMLocation();
|
2020-11-09 20:59:13 +00:00
|
|
|
this.renderThumbnail();
|
2019-12-16 14:15:02 +00:00
|
|
|
|
|
|
|
this.localVideoId = null;
|
|
|
|
this.bindHoverHandler();
|
|
|
|
if (!config.disableLocalVideoFlip) {
|
|
|
|
this._buildContextMenu();
|
2016-02-09 10:19:43 +00:00
|
|
|
}
|
2019-12-16 14:15:02 +00:00
|
|
|
this.emitter = emitter;
|
|
|
|
|
|
|
|
Object.defineProperty(this, 'id', {
|
|
|
|
get() {
|
|
|
|
return APP.conference.getMyUserId();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.initBrowserSpecificProperties();
|
2017-06-30 17:40:55 +00:00
|
|
|
|
2019-12-16 14:15:02 +00:00
|
|
|
this.container.onclick = this._onContainerClick;
|
2015-06-23 08:00:46 +00:00
|
|
|
}
|
|
|
|
|
2019-12-16 14:15:02 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
createContainer() {
|
|
|
|
const containerSpan = document.createElement('span');
|
|
|
|
|
|
|
|
containerSpan.classList.add('videocontainer');
|
|
|
|
containerSpan.id = this.videoSpanId;
|
|
|
|
|
|
|
|
return containerSpan;
|
|
|
|
}
|
2015-06-23 08:00:46 +00:00
|
|
|
|
2019-12-16 14:15:02 +00:00
|
|
|
/**
|
2020-11-09 20:59:13 +00:00
|
|
|
* Renders the thumbnail.
|
2019-12-16 14:15:02 +00:00
|
|
|
*/
|
2020-11-09 20:59:13 +00:00
|
|
|
renderThumbnail(isHovered = false) {
|
|
|
|
ReactDOM.render(
|
|
|
|
<Provider store = { APP.store }>
|
|
|
|
<I18nextProvider i18n = { i18next }>
|
|
|
|
<Thumbnail participantID = { this.id } isHovered = { isHovered } />
|
|
|
|
</I18nextProvider>
|
|
|
|
</Provider>, this.container);
|
2019-12-16 14:15:02 +00:00
|
|
|
}
|
2015-06-23 08:00:46 +00:00
|
|
|
|
2019-12-16 14:15:02 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {*} stream
|
|
|
|
*/
|
|
|
|
changeVideo(stream) {
|
|
|
|
this.localVideoId = `localVideo_${stream.getId()}`;
|
|
|
|
|
|
|
|
// eslint-disable-next-line eqeqeq
|
|
|
|
const isVideo = stream.videoType != 'desktop';
|
|
|
|
const settings = APP.store.getState()['features/base/settings'];
|
|
|
|
|
|
|
|
this._enableDisableContextMenu(isVideo);
|
|
|
|
this.setFlipX(isVideo ? settings.localFlipX : false);
|
|
|
|
|
|
|
|
const endedHandler = () => {
|
|
|
|
this._notifyOfStreamEnded();
|
|
|
|
stream.off(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
|
|
|
|
};
|
2017-10-12 23:02:29 +00:00
|
|
|
|
2019-12-16 14:15:02 +00:00
|
|
|
stream.on(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
|
|
|
|
}
|
2015-06-23 08:00:46 +00:00
|
|
|
|
2019-12-16 14:15:02 +00:00
|
|
|
/**
|
|
|
|
* Notify any subscribers of the local video stream ending.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_notifyOfStreamEnded() {
|
|
|
|
if (this.streamEndedCallback) {
|
|
|
|
this.streamEndedCallback(this.id);
|
2017-07-14 19:22:27 +00:00
|
|
|
}
|
2018-05-21 22:10:43 +00:00
|
|
|
}
|
|
|
|
|
2019-12-16 14:15:02 +00:00
|
|
|
/**
|
|
|
|
* Shows or hides the local video container.
|
|
|
|
* @param {boolean} true to make the local video container visible, false
|
|
|
|
* otherwise
|
|
|
|
*/
|
|
|
|
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');
|
|
|
|
|
|
|
|
// We still show/hide it as we need to overwrite the style property if we
|
|
|
|
// want our action to take effect. Toggling the display property through
|
|
|
|
// the above css class didn't succeed in overwriting the style.
|
|
|
|
if (visible) {
|
|
|
|
this.$container.show();
|
|
|
|
} else {
|
|
|
|
this.$container.hide();
|
|
|
|
}
|
2016-05-01 18:35:18 +00:00
|
|
|
}
|
|
|
|
|
2019-12-16 14:15:02 +00:00
|
|
|
/**
|
|
|
|
* Sets the flipX state of the video.
|
|
|
|
* @param val {boolean} true for flipped otherwise false;
|
|
|
|
*/
|
|
|
|
setFlipX(val) {
|
|
|
|
this.emitter.emit(UIEvents.LOCAL_FLIPX_CHANGED, val);
|
|
|
|
if (!this.localVideoId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (val) {
|
|
|
|
this.selectVideoElement().addClass('flipVideoX');
|
|
|
|
} else {
|
|
|
|
this.selectVideoElement().removeClass('flipVideoX');
|
|
|
|
}
|
2016-05-07 01:50:37 +00:00
|
|
|
}
|
|
|
|
|
2019-12-16 14:15:02 +00:00
|
|
|
/**
|
|
|
|
* Builds the context menu for the local video.
|
|
|
|
*/
|
|
|
|
_buildContextMenu() {
|
|
|
|
$.contextMenu({
|
|
|
|
selector: `#${this.videoSpanId}`,
|
|
|
|
zIndex: 10000,
|
|
|
|
items: {
|
|
|
|
flip: {
|
|
|
|
name: 'Flip',
|
|
|
|
callback: () => {
|
|
|
|
const { store } = APP;
|
|
|
|
const val = !store.getState()['features/base/settings']
|
|
|
|
.localFlipX;
|
|
|
|
|
|
|
|
this.setFlipX(val);
|
|
|
|
store.dispatch(updateSettings({
|
|
|
|
localFlipX: val
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
events: {
|
|
|
|
show(options) {
|
|
|
|
options.items.flip.name
|
|
|
|
= APP.translation.generateTranslationHTML(
|
|
|
|
'videothumbnail.flip');
|
2016-05-07 01:50:37 +00:00
|
|
|
}
|
|
|
|
}
|
2019-12-16 14:15:02 +00:00
|
|
|
});
|
2017-10-12 23:02:29 +00:00
|
|
|
}
|
2016-05-07 01:50:37 +00:00
|
|
|
|
2019-12-16 14:15:02 +00:00
|
|
|
/**
|
|
|
|
* Enables or disables the context menu for the local video.
|
|
|
|
* @param enable {boolean} true for enable, false for disable
|
|
|
|
*/
|
|
|
|
_enableDisableContextMenu(enable) {
|
|
|
|
if (this.$container.contextMenu) {
|
|
|
|
this.$container.contextMenu(enable);
|
|
|
|
}
|
2018-08-08 18:48:23 +00:00
|
|
|
}
|
|
|
|
|
2019-12-16 14:15:02 +00:00
|
|
|
/**
|
|
|
|
* Places the {@code LocalVideo} in the DOM based on the current video layout.
|
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
updateDOMLocation() {
|
|
|
|
if (!this.container) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (this.container.parentElement) {
|
|
|
|
this.container.parentElement.removeChild(this.container);
|
|
|
|
}
|
2018-08-08 18:48:23 +00:00
|
|
|
|
2019-12-16 14:15:02 +00:00
|
|
|
const appendTarget = shouldDisplayTileView(APP.store.getState())
|
|
|
|
? document.getElementById('localVideoTileViewContainer')
|
|
|
|
: document.getElementById('filmstripLocalVideoThumbnail');
|
2018-08-08 18:48:23 +00:00
|
|
|
|
2019-12-16 14:15:02 +00:00
|
|
|
appendTarget && appendTarget.appendChild(this.container);
|
|
|
|
}
|
|
|
|
}
|