jiti-meet/react/features/large-video/components/LargeVideo.web.js

351 lines
11 KiB
JavaScript
Raw Normal View History

// @flow
import React, { Component } from 'react';
import VideoLayout from '../../../../modules/UI/videolayout/VideoLayout';
import { getLocalParticipant } from '../../base/participants';
import { Watermarks } from '../../base/react';
import { connect } from '../../base/redux';
import { getVideoTrackByParticipant } from '../../base/tracks';
import { setColorAlpha } from '../../base/util';
import { StageParticipantNameLabel } from '../../display-name';
import { FILMSTRIP_BREAKPOINT, isFilmstripResizable } from '../../filmstrip';
import { getVerticalViewMaxWidth } from '../../filmstrip/functions.web';
import { getLargeVideoParticipant } from '../../large-video/functions';
import { SharedVideo } from '../../shared-video/components/web';
2020-05-20 10:57:03 +00:00
import { Captions } from '../../subtitles/';
import { setTileView } from '../../video-layout/actions';
import { setSeeWhatIsBeingShared } from '../actions.web';
import ScreenSharePlaceholder from './ScreenSharePlaceholder.web';
feat(recording): frontend logic can support live streaming and recording (#2952) * feat(recording): frontend logic can support live streaming and recording Instead of either live streaming or recording, now both can live together. The changes to facilitate such include the following: - Killing the state storing in Recording.js. Instead state is stored in the lib and updated in redux for labels to display the necessary state updates. - Creating a new container, Labels, for recording labels. Previously labels were manually created and positioned. The container can create a reasonable number of labels and only the container itself needs to be positioned with CSS. The VideoQualityLabel has been shoved into the container as well because it moves along with the recording labels. - The action for updating recording state has been modified to enable updating an array of recording sessions to support having multiple sessions. - Confirmation dialogs for stopping and starting a file recording session have been created, as they previously were jquery modals opened by Recording.js. - Toolbox.web displays live streaming and recording buttons based on configuration instead of recording availability. - VideoQualityLabel and RecordingLabel have been simplified to remove any positioning logic, as the Labels container handles such. - Previous recording state update logic has been moved into the RecordingLabel component. Each RecordingLabel is in charge of displaying state for a recording session. The display UX has been left alone. - Sipgw availability is no longer broadcast so remove logic depending on its state. Some moving around of code was necessary to get around linting errors about the existing code being too deeply nested (even though I didn't touch it). * work around lib-jitsi-meet circular dependency issues * refactor labels to use html base * pass in translation keys to video quality label * add video quality classnames for torture tests * break up, rearrange recorder session update listener * add comment about disabling startup resize animation * rename session to sessionData * chore(deps): update to latest lib for recording changes
2018-05-16 14:00:16 +00:00
feat(quality-slider): initial implementation (#1817) * feat(quality-slider): initial implementation - Add new menu button with an Inline Dialog slider for selecting received video quality. - Place P2P status in redux store for the Inline Dialog to display a warning about not respecting video quality selection. - Respond to data channel open events by setting receive video quality. This is for lonely call cases where a setting is set before the data channel is open. - Remove dropdown menu from video status label and clean up related js and css. * first pass at addressing feedback - Move VideoStatusLabel to video-quality directory. - Rename VideoStatusLabel to VideoQualityLabel. - Open VideoQualitydialog from VideoQualityLabel. - New CSS for making VideoQualityLabel display properly. - Do not render VideoQualityLabel in filmstrip only instead of hiding with css. - Remove tooltip from VideoQualityLabel. - Show LD, SD, HD labels in VideoQualityLabel. - Remove action SET_LARGE_VIDEO_HD_STATUS from conference. - Create new action UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION in large-video. - Move VideoQualityButton into video-quality directory. - General renaming (medium -> standard, menu -> dialog). - Render P2P message between title and slider. - Add padding to slider for displacement caused by P2P message's new placement. - Fix display issue with VideoQualityButton displaying out of line in the primary toolbar. * second pass at addressing feedback - Fix p2p inline message color - Force labels to break on words - Resolve rebase issues, including only dispatching quality update on change. Before there was double calling of dispatch produced by an IE11 workaround. This breaks now when setting audio only mode to true twice. - Rename some instances of quality to definition * rename to data channel opened * do not show p2p in audio only * stop toggle audio only icon automatically * remove fixme about toolbar button * find closest resolution for label * toggle dialog on button click * redo last commit for both button and label
2017-08-09 19:40:03 +00:00
declare var interfaceConfig: Object;
type Props = {
/**
2021-11-04 21:10:43 +00:00
* The alpha(opacity) of the background.
*/
_backgroundAlpha: number,
/**
* The user selected background color.
*/
_customBackgroundColor: string,
/**
* The user selected background image url.
*/
_customBackgroundImageUrl: string,
/**
* Prop that indicates whether the chat is open.
*/
_isChatOpen: boolean,
/**
* Used to determine the value of the autoplay attribute of the underlying
* video element.
*/
_noAutoPlayVideo: boolean,
/**
* Whether or not the filmstrip is resizable.
*/
_resizableFilmstrip: boolean,
/**
* Whether or not to show dominant speaker badge.
*/
_showDominantSpeakerBadge: boolean,
/**
* The width of the vertical filmstrip (user resized).
*/
_verticalFilmstripWidth: ?number,
/**
* The max width of the vertical filmstrip.
*/
_verticalViewMaxWidth: number,
/**
* Whether or not the filmstrip is visible.
*/
_visibleFilmstrip: boolean,
/**
* The large video participant id.
*/
_largeVideoParticipantId: string,
/**
* Whether or not the screen sharing is on.
*/
_isScreenSharing: boolean,
/**
* Whether or not the screen sharing is visible.
*/
_seeWhatIsBeingShared: boolean,
/**
* The Redux dispatch function.
*/
dispatch: Function
}
2021-11-04 21:10:43 +00:00
/** .
* Implements a React {@link Component} which represents the large video (a.k.a.
2021-11-04 21:10:43 +00:00
* The conference participant who is on the local stage) on Web/React.
*
2021-11-04 21:10:43 +00:00
* @augments Component
*/
class LargeVideo extends Component<Props> {
2021-11-04 21:10:43 +00:00
_tappedTimeout: ?TimeoutID;
_containerRef: Object;
_wrapperRef: Object;
/**
* Constructor of the component.
*
* @inheritdoc
*/
constructor(props) {
super(props);
this._containerRef = React.createRef();
this._wrapperRef = React.createRef();
this._clearTapTimeout = this._clearTapTimeout.bind(this);
this._onDoubleTap = this._onDoubleTap.bind(this);
this._updateLayout = this._updateLayout.bind(this);
}
/**
* Implements {@code Component#componentDidUpdate}.
*
* @inheritdoc
*/
componentDidUpdate(prevProps: Props) {
const { _visibleFilmstrip, _isScreenSharing, _seeWhatIsBeingShared, _largeVideoParticipantId } = this.props;
if (prevProps._visibleFilmstrip !== _visibleFilmstrip) {
this._updateLayout();
}
if (prevProps._isScreenSharing !== _isScreenSharing && !_isScreenSharing) {
this.props.dispatch(setSeeWhatIsBeingShared(false));
}
if (_isScreenSharing && _seeWhatIsBeingShared) {
VideoLayout.updateLargeVideo(_largeVideoParticipantId, true, true);
}
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {React$Element}
*/
render() {
const {
_isChatOpen,
_noAutoPlayVideo,
_showDominantSpeakerBadge,
_isScreenSharing,
_seeWhatIsBeingShared
} = this.props;
2022-08-25 09:30:39 +00:00
const style = this._getCustomStyles();
const className = `videocontainer${_isChatOpen ? ' shift-right' : ''}`;
return (
<div
className = { className }
id = 'largeVideoContainer'
ref = { this._containerRef }
style = { style }>
<SharedVideo />
<div id = 'etherpad' />
<Watermarks />
<div
id = 'dominantSpeaker'
onTouchEnd = { this._onDoubleTap }>
<div className = 'dynamic-shadow' />
<div id = 'dominantSpeakerAvatarContainer' />
</div>
<div id = 'remotePresenceMessage' />
<span id = 'remoteConnectionMessage' />
<div id = 'largeVideoElementsContainer'>
fix(large-video): do not show background for Firefox and temasys (#2316) * ref(large-video): reactify background This is pre-requisite work for disabling the background on certain browsers, namely Firefox. By moving the component to react, and in general encapsulating background logic, selectively disabling the background will be easier. The component was left for LargeVideo to update so it can continue to coordinate update timing with the actual large video display. If the background were moved completely into react and redux with LargeVideo, then background updates would occur before large video updates causing visual jank. * fix(large-video): do not show background for Firefox and temasys Firefox has performance issues with adding filter effects on animated elements. On temasys, the background videos weren't really displaying anyway. * some props refactoring Instead of passing in classes to LargeVideoBackground, rely on explicit props. At some point LargeVideo will have to be reactified and the relationsihp between it and LargeVideoBackground might change, so for now make use of props to be explicit about how LargeVideoBackground can be modified. Also, set the jitsiTrack to display on LargeVideoBackground to null if the background is not displayed. This was an existing optimization, although previously done with pausing and playing. * squash: use newly exposed RTCBrowserType * squash: rebase and use new lib browser util * squash: move hiding logic all into LargeVideo * squash: remove hiding of background on stream change. hopefully doesnt break anything
2018-02-13 00:29:29 +00:00
<div id = 'largeVideoBackgroundContainer' />
{/*
* FIXME: the architecture of elements related to the large
* video and the naming. The background is not part of
* largeVideoWrapper because we are controlling the size of
* the video through largeVideoWrapper. That's why we need
* another container for the background and the
* largeVideoWrapper in order to hide/show them.
*/}
<div
id = 'largeVideoWrapper'
onTouchEnd = { this._onDoubleTap }
ref = { this._wrapperRef }
role = 'figure' >
{_isScreenSharing && !_seeWhatIsBeingShared ? <ScreenSharePlaceholder /> : <video
autoPlay = { !_noAutoPlayVideo }
id = 'largeVideo'
muted = { true }
playsInline = { true } /* for Safari on iOS to work */ />}
</div>
</div>
{ interfaceConfig.DISABLE_TRANSCRIPTION_SUBTITLES
|| <Captions /> }
{_showDominantSpeakerBadge && <StageParticipantNameLabel />}
</div>
);
}
_updateLayout: () => void;
/**
* Refreshes the video layout to determine the dimensions of the stage view.
* If the filmstrip is toggled it adds CSS transition classes and removes them
* when the transition is done.
*
* @returns {void}
*/
_updateLayout() {
const { _verticalFilmstripWidth, _resizableFilmstrip } = this.props;
if (_resizableFilmstrip && _verticalFilmstripWidth >= FILMSTRIP_BREAKPOINT) {
this._containerRef.current.classList.add('transition');
this._wrapperRef.current.classList.add('transition');
VideoLayout.refreshLayout();
setTimeout(() => {
this._containerRef.current && this._containerRef.current.classList.remove('transition');
this._wrapperRef.current && this._wrapperRef.current.classList.remove('transition');
}, 1000);
} else {
VideoLayout.refreshLayout();
}
}
2021-11-04 21:10:43 +00:00
_clearTapTimeout: () => void;
/**
* Clears the '_tappedTimout'.
*
* @private
* @returns {void}
*/
_clearTapTimeout() {
clearTimeout(this._tappedTimeout);
this._tappedTimeout = undefined;
}
/**
* Creates the custom styles object.
*
* @private
* @returns {Object}
*/
2022-08-25 09:30:39 +00:00
_getCustomStyles() {
const styles = {};
const {
_customBackgroundColor,
_customBackgroundImageUrl,
_verticalFilmstripWidth,
_verticalViewMaxWidth,
_visibleFilmstrip
} = this.props;
styles.backgroundColor = _customBackgroundColor || interfaceConfig.DEFAULT_BACKGROUND;
if (this.props._backgroundAlpha !== undefined) {
const alphaColor = setColorAlpha(styles.backgroundColor, this.props._backgroundAlpha);
styles.backgroundColor = alphaColor;
}
if (_customBackgroundImageUrl) {
styles.backgroundImage = `url(${_customBackgroundImageUrl})`;
styles.backgroundSize = 'cover';
}
if (_visibleFilmstrip && _verticalFilmstripWidth >= FILMSTRIP_BREAKPOINT) {
styles.width = `calc(100% - ${_verticalViewMaxWidth || 0}px)`;
}
return styles;
}
2021-11-04 21:10:43 +00:00
_onDoubleTap: () => void;
/**
* Sets view to tile view on double tap.
*
* @param {Object} e - The event.
* @private
* @returns {void}
*/
_onDoubleTap(e) {
e.stopPropagation();
e.preventDefault();
if (this._tappedTimeout) {
this._clearTapTimeout();
this.props.dispatch(setTileView(true));
} else {
this._tappedTimeout = setTimeout(this._clearTapTimeout, 300);
}
}
}
/**
* Maps (parts of) the Redux state to the associated LargeVideo props.
*
* @param {Object} state - The Redux state.
* @private
* @returns {Props}
*/
function _mapStateToProps(state) {
const testingConfig = state['features/base/config'].testing;
const { backgroundColor, backgroundImageUrl } = state['features/dynamic-branding'];
const { isOpen: isChatOpen } = state['features/chat'];
const { width: verticalFilmstripWidth, visible } = state['features/filmstrip'];
const { hideDominantSpeakerBadge } = state['features/base/config'];
const tracks = state['features/base/tracks'];
const localParticipantId = getLocalParticipant(state)?.id;
const largeVideoParticipant = getLargeVideoParticipant(state);
const videoTrack = getVideoTrackByParticipant(tracks, largeVideoParticipant);
const localParticipantisSharingTheScreen = largeVideoParticipant?.id?.includes(localParticipantId);
const isScreenSharing = localParticipantisSharingTheScreen && videoTrack?.videoType === 'desktop';
return {
_backgroundAlpha: state['features/base/config'].backgroundAlpha,
_customBackgroundColor: backgroundColor,
_customBackgroundImageUrl: backgroundImageUrl,
_isChatOpen: isChatOpen,
_isScreenSharing: isScreenSharing,
_largeVideoParticipantId: largeVideoParticipant?.id,
_noAutoPlayVideo: testingConfig?.noAutoPlayVideo,
_resizableFilmstrip: isFilmstripResizable(state),
_seeWhatIsBeingShared: state['features/large-video'].seeWhatIsBeingShared,
_showDominantSpeakerBadge: !hideDominantSpeakerBadge,
_verticalFilmstripWidth: verticalFilmstripWidth.current,
_verticalViewMaxWidth: getVerticalViewMaxWidth(state),
_visibleFilmstrip: visible
};
}
export default connect(_mapStateToProps)(LargeVideo);