diff --git a/src/componets/AudioVideo/AudioVideo.js b/src/componets/AudioVideo/AudioVideo.js index 14cdac0d72f3b045550eb1f0488b7dc114ef01bd..4b59794b9d30fc114024345ad4ff9d9bbdc5eaa8 100644 --- a/src/componets/AudioVideo/AudioVideo.js +++ b/src/componets/AudioVideo/AudioVideo.js @@ -21,7 +21,7 @@ import icVideo from 'Assets/img/icons/ic_menu_videocall_normal.svg'; import { KeyboardArrowUp } from '@material-ui/icons'; import Draggable from 'react-draggable'; import { MenuAppBar } from '../../containers'; -import { profileSelectors } from 'Resource/profile'; +import { profileSelectors } from '../../core/resource/profile'; import BrowserNotification from './BrowserNotification/BrowserNotification'; import ConfirmDialog from '../Modals/ConfirmDialog/ConfirmDialog'; import AudioVideoControls from './AudioVideoControls/AudioVideoControls'; @@ -70,6 +70,7 @@ const getUser = (state) => { minimized: audioVideoSelectors.isMinimized(state), getMemberById: (roomId, phoneId) => roomListSelectors.getMemberByPhoneId(state, roomId, phoneId), isUnreadInChat: conversationSelectors.getUnreadStatusForCall(state), + membersWithVideo: audioVideoSelectors.getOtherMembersWithVideo(state, profileSelectors.getPhoneId(state)), }), dispatch => ({ actions: bindActionCreators({ openMessageModal: notificationActions.openMessageModal, @@ -738,7 +739,9 @@ class AudioVideo extends Component { } render() { - const { personalData, classes, t, callData, anonymousUser, roomToJoinName, minimized, isUnreadInChat } = this.props; + const { personalData, classes, t, callData, anonymousUser, + roomToJoinName, minimized, isUnreadInChat, membersWithVideo, + } = this.props; const { isInFullscreen, fullScreenData } = this.state; const { members: membersAll, muted, speakerMuted, isAdmin, video, p2p, screenSharing } = callData; @@ -1077,7 +1080,7 @@ class AudioVideo extends Component { } {showFullScreenMode && - stylesFunc(theme); @@ -12,11 +15,17 @@ class AudioVideoFullScreenView extends Component { constructor(props) { super(props); - this.state = { showControls: false }; + this.state = { + showControls: false, + feedData: [], + }; this.controlsTimeout = null; this.fullScreenRef = createRef(); this.ssRef = createRef(); + this.throttleFunc = throttle((data) => { + this.setState({ feedData: data }); + }, 1000); } isPromise = (value) => { @@ -31,12 +40,49 @@ class AudioVideoFullScreenView extends Component { this.setFullScreen(); if (isVideo) { setMemberMediaFeed(this.fullScreenRef.current, isSelf, member); + this.orderFeedsInit(); } else if (isScreenSharing && this.ssRef && this.ssRef.current) { setRemoteScreenShareElement(this.ssRef.current); + this.orderFeedsInit(); } } } + componentDidUpdate(prevProps) { + const { membersWithVideo } = this.props; + const { feedData } = this.state; + const oldSpeekers = prevProps.callData.activeSpeakers ? prevProps.callData.activeSpeakers.join(', ') : ''; + const newSpeekers = this.props.callData.activeSpeakers ? this.props.callData.activeSpeakers.join(', ') : ''; + const prevLen = prevProps.membersWithVideo.length; + const newLen = membersWithVideo.length; + const changed = []; + + membersWithVideo.forEach(m => { + const { phone_id, isAudioMuted } = m; + const memberInState = feedData.find(d => d.phone_id === phone_id); + if (memberInState && memberInState.isAudioMuted !== isAudioMuted) { + changed.push(m); + } + }); + + if (changed.length > 0) { + const result = feedData.map(item => ({ + ...item, + ...changed.find(({ phone_id }) => phone_id === item.phone_id), + })); + + this.setState({ feedData: result }); + } + + if (oldSpeekers !== newSpeekers) { + this.reorderFeedsChangedActiveSpeakers(); + } + + if (prevLen !== newLen) { + this.reorderFeedsChangedMembers(prevProps.membersWithVideo, membersWithVideo, prevLen, newLen); + } + } + componentWillUnmount() { const { fullScreenData, memberSelf, disableFullScreenMode, setMemberMediaFeed, @@ -45,7 +91,7 @@ class AudioVideoFullScreenView extends Component { if (isVideo) { setMemberMediaFeed(null, isSelf, member); - setMemberMediaFeed(null, true, memberSelf) + setMemberMediaFeed(null, true, memberSelf); } this.removeEventListeners(); @@ -118,6 +164,61 @@ class AudioVideoFullScreenView extends Component { } } + orderFeedsInit = () => { + const { fullScreenData, membersWithVideo } = this.props; + const { member } = fullScreenData; + const memberOnFullscreenId = member ? member.phone_id : ''; + + this.setState({ feedData: membersWithVideo.filter(m => m.phone_id !== memberOnFullscreenId) }); + } + + reorderFeedsChangedActiveSpeakers = () => { + const { callData, fullScreenData, membersWithVideo } = this.props; + const { feedData } = this.state; + const { member } = fullScreenData; + const { activeSpeakers } = callData; + const newSpeakers = activeSpeakers ? activeSpeakers : []; + const newTopSpeakerName = newSpeakers[newSpeakers.length - 1]; + const memberOnFullscreenName = member ? member.displayName : ''; + + if (newTopSpeakerName) { + const currentTopSpeaker = feedData[0]; + if (currentTopSpeaker && currentTopSpeaker.displayName !== newTopSpeakerName && memberOnFullscreenName !== newTopSpeakerName) { + const newTopSpeaker = membersWithVideo.find(m => m.displayName === newTopSpeakerName); + if (newTopSpeaker) { + const newFeedData = [newTopSpeaker, ...feedData.filter(d => d.displayName !== newTopSpeakerName)]; + this.throttleFunc(newFeedData); + } + } + } + } + + /** + * @param {Array} oldList + * @param {Array} newList + * @param {Number} prevLen + * @param {Number} newLen + * @returns void + */ + reorderFeedsChangedMembers = (oldList, newList, prevLen, newLen) => { + const { member } = this.props; + const memberOnFullscreenId = member ? member.phone_id : ''; + + if (newLen > prevLen) { + this.setState({ + feedData: [ + ...differenceBy( + newList.filter(d => d.phone_id !== memberOnFullscreenId), + oldList, + 'phone_id', + ), + ...oldList], + }); + } else { + this.orderFeedsInit(); + } + } + renderFullScreenVideo = () => { const { classes } = this.props; @@ -125,8 +226,8 @@ class AudioVideoFullScreenView extends Component {
{this.renderControls()} - {this.renderFeeds()} - {this.renderActiveSpeekers()} + {this.renderMyFeed()} + {this.renderOtherFeeds()}
) @@ -139,8 +240,8 @@ class AudioVideoFullScreenView extends Component {
{this.renderControls()} - {this.renderFeeds()} - {this.renderActiveSpeekers()} + {this.renderMyFeed()} + {this.renderOtherFeeds()}
@@ -166,14 +267,47 @@ class AudioVideoFullScreenView extends Component { return null; } - renderFeeds = () => { + renderMyFeed = () => { const { classes, t, setMemberMediaFeed, memberSelf, isAdmin } = this.props; + if (memberSelf && memberSelf.video) { + return ( + +
+ +
+
+ ); + } + + return null; + } + + renderOtherFeeds = () => { + const { classes, fullScreenData, setRemoteScreenShareElement, + callData, t, isAdmin, setMemberMediaFeed, + } = this.props; + const { feedData } = this.state; + const { isVideo } = fullScreenData; + const { screenSharer } = callData; + return ( -
- +
+ {this.renderActiveSpeekers()} + {isVideo && screenSharer && + + } + {feedData.map(data => { + return ( +
+ +
+ ); + })}
); @@ -186,14 +320,10 @@ class AudioVideoFullScreenView extends Component { if (!p2p) { return ( - -
-
- - {talking} -
-
-
+
+ + {talking} +
); } @@ -224,6 +354,7 @@ AudioVideoFullScreenView.propTypes = { setRemoteScreenShareElement: PropTypes.func.isRequired, memberSelf: PropTypes.object.isRequired, isAdmin: PropTypes.bool, + membersWithVideo: PropTypes.array.isRequired, }; export default withStyles(styles)(AudioVideoFullScreenView); diff --git a/src/componets/AudioVideo/AudioVideoFullScreenView/AudioVideoFullScreenView.styles.js b/src/componets/AudioVideo/AudioVideoFullScreenView/AudioVideoFullScreenView.styles.js index 63feb94b9eb229af6d54b707c1c508ff2d0f1478..0dc6d26b505dda2b3937cef5024659b095ee93f2 100644 --- a/src/componets/AudioVideo/AudioVideoFullScreenView/AudioVideoFullScreenView.styles.js +++ b/src/componets/AudioVideo/AudioVideoFullScreenView/AudioVideoFullScreenView.styles.js @@ -2,6 +2,17 @@ export default theme => ({ wrapp: { width: '100%', height: '100%', + scrollbarColor: `${theme.palette.themeColors.colors.scrollColor} transparent !important`, + '& *::-webkit-scrollbar': { + WebkitAppearance: 'none', + width: 12, + }, + '& *::-webkit-scrollbar-thumb': { + borderRadius: 10, + backgroundColor: theme.palette.themeColors.colors.scrollColor, + width: 12, + boxShadow: 'none', + }, }, fullScreenWrapp: { '& video': { @@ -31,7 +42,7 @@ export default theme => ({ left: '50%', transform: 'translateX(-50%)', }, - feedsWrapp: { + myFeedWrapp: { height: 114, width: 179, position: 'absolute', @@ -39,22 +50,29 @@ export default theme => ({ right: 49, zIndex: 2147483647, }, - speakersWrapp: { + otherFeedsWrapp: { + maxHeight: '60%', + width: 190, position: 'absolute', - top: 100, - right: 100, + top: 140, + right: 49, zIndex: 2147483647, + overflowX: 'hidden', + overflowY: 'scroll', + }, + speakersWrapp: { background: theme.palette.themeColors.audioVideo.audioVideoMember.memberName.background, opacity: 0.8, - cursor: 'pointer', - }, - infoSpeakerNames: { + width: 179, color: theme.palette.themeColors.audioVideo.infoLineSpeakersColor, - display: 'flex', + padding: '4px 10px', overflow: 'hidden', textOverflow: 'ellipsis', - alignItems: 'center', - padding: '4px 10px', + whiteSpace: 'nowrap', + }, + feed: { + height: 114, + width: 179, }, speakerIcon: { color: theme.palette.themeColors.audioVideo.infoLineSpeakersColor, diff --git a/src/componets/AudioVideo/AudioVideoMember/AudioVideoMember.js b/src/componets/AudioVideo/AudioVideoMember/AudioVideoMember.js index c61ab68b17105a63720326a81683e44eb9463ed1..15ce0201aa73c25e03d7483c54b7e20696ef2a06 100644 --- a/src/componets/AudioVideo/AudioVideoMember/AudioVideoMember.js +++ b/src/componets/AudioVideo/AudioVideoMember/AudioVideoMember.js @@ -13,7 +13,6 @@ import stylesFunc from './AudioVideoMember.styles'; const styles = theme => (stylesFunc(theme)); class AudioVideoMember extends Component { - componentDidMount() { const { isSelf, callbacks, member } = this.props; @@ -24,6 +23,16 @@ class AudioVideoMember extends Component { } } + componentDidUpdate(prevProps, prevState) { + if (this.props.fromFullScreenView) { + if (prevProps.member && this.props.member) { + if (prevProps.member.phone_id !== this.props.member.phone_id) { + this.props.callbacks.setMemberMediaFeed(this.feed, this.props.isSelf, this.props.member); + } + } + } + } + toggleFullScreen = (isFromAvatar) => { //if (isFromAvatar === true) { // const { member, isSelf, isFullScreen, p2p } = this.props; @@ -92,7 +101,7 @@ class AudioVideoMember extends Component { } render() { - const { member, classes, t, isSelf, isAdmin, isFullScreen, p2p, isGone, screenSharer, callbacks, contactStatus, activeSpeakers } = this.props; + const { member, classes, t, isSelf, isFullScreen, p2p, isGone, screenSharer, activeSpeakers, fromFullScreenView } = this.props; const feedClass = `${classes.feed} ${!member || !member.video ? classes.hiddenFeed : ''}`; const isScreenSharer = screenSharer && member && screenSharer.phone_id === member.phone_id; @@ -163,7 +172,7 @@ class AudioVideoMember extends Component { {(!isSelf || p2p) &&
- {member && member.isActive && + {member && member.isActive && !fromFullScreenView && } - {member && member.isActive && member.video && isFullScreen && + {member && member.isActive && member.video && isFullScreen && !fromFullScreenView && @@ -196,6 +205,7 @@ AudioVideoMember.propTypes = { isSelf: PropTypes.bool, callbacks: PropTypes.object, t: PropTypes.func, + fromFullScreenView: PropTypes.bool, }; export default withStyles(styles)(AudioVideoMember); diff --git a/src/componets/AudioVideo/AudioVideoMembersView/AudioVideoMembersView.js b/src/componets/AudioVideo/AudioVideoMembersView/AudioVideoMembersView.js index 6aa8c19eb5bed3bdf72ec5a4b3072977933d32d5..b396150d741ef7fe888c3cae8520d57442cb67bf 100644 --- a/src/componets/AudioVideo/AudioVideoMembersView/AudioVideoMembersView.js +++ b/src/componets/AudioVideo/AudioVideoMembersView/AudioVideoMembersView.js @@ -5,6 +5,7 @@ import PropTypes from 'prop-types'; import stylesFunc from './AudioVideoMembersView.styles'; import { isMobile } from 'react-device-detect'; import AudioVideoMemberWithMenu from '../AudioVideoMember/AudioVideoMemberWithMenu'; +import AudioVideoScreenSharing from '../AudioVideoScreenSharing/AudioVideoScreenSharing'; import icFullScreen from "Assets/img/icons/ic_user_full_screen.svg"; import { withTranslation } from 'react-i18next'; @@ -22,12 +23,6 @@ class AudioVideoMembersView extends PureComponent { } }; - setRemoteScreenShareElement = (el) => { - if (this.props.callbacks.setRemoteScreenShareElement) { - this.props.callbacks.setRemoteScreenShareElement(el); - } - }; - render() { const { t, classes, data } = this.props; const { callData, maxSteps, fullScreenMember, memberSelf, @@ -71,10 +66,9 @@ class AudioVideoMembersView extends PureComponent { onClick={this.handleBack} disabled={activeMembersStep === 0}> - {remoteScreenSharing && -
-
+ {remoteScreenSharing && this.props.callbacks.setRemoteScreenShareElement && + } {!fullScreenMember && !remoteScreenSharing &&
diff --git a/src/componets/AudioVideo/AudioVideoScreenSharing/AudioVideoScreenSharing.js b/src/componets/AudioVideo/AudioVideoScreenSharing/AudioVideoScreenSharing.js new file mode 100644 index 0000000000000000000000000000000000000000..2ed26563bacbc5e41e6b31e89f6be570e64a9b24 --- /dev/null +++ b/src/componets/AudioVideo/AudioVideoScreenSharing/AudioVideoScreenSharing.js @@ -0,0 +1,24 @@ +import React, { useRef, useEffect } from 'react'; +import PropTypes from 'prop-types'; + +const AudioVideoScreenSharing = (props) => { + const sharingRef = useRef(null); + const { classWrap, setRemoteScreenShareElement } = props; + + useEffect(() => { + setRemoteScreenShareElement(sharingRef.current); + }, []); + + return ( +
+
+ ); +}; + +AudioVideoScreenSharing.propTypes = { + setRemoteScreenShareElement: PropTypes.func.isRequired, + classWrap: PropTypes.string.isRequired, +}; + +export default AudioVideoScreenSharing; diff --git a/src/core/resource/audioVideo/modules/AudioVideo.module.js b/src/core/resource/audioVideo/modules/AudioVideo.module.js index 1ab1c788980dced69a61fc195aa2e74d01ee186c..a55a6cad487b9717c29a79771e69dbf390321e8e 100644 --- a/src/core/resource/audioVideo/modules/AudioVideo.module.js +++ b/src/core/resource/audioVideo/modules/AudioVideo.module.js @@ -311,6 +311,25 @@ const isInFullScreenCall = (state) => { return !instance.minimizeRequested; }; +const getOtherMembersWithVideo = (state, myPhoneId) => { + const result = []; + const instance = getInstance(state); + + if (!instance || !instance.conferenceId) { + return result; + } + + if (instance.members && Array.isArray(instance.members)) { + instance.members.forEach(m => { + if (m.video && m.phone_id !== myPhoneId) { + result.push(m); + } + }); + } + + return result; +}; + const audioVideoActions = { incomingCall, endCall, @@ -352,6 +371,7 @@ const audioVideoSelectors = { isMinimized, getAllActiveCallsRaw, isInFullScreenCall, + getOtherMembersWithVideo, }; export default audioVideo;