diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 104e2993d80..eff865f20c3 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -76,16 +76,22 @@ limitations under the License. &.mx_VideoFeed_voice { // We don't want to collide with the call controls that have 52px of height - padding-bottom: 52px; + margin-bottom: 52px; background-color: $inverted-bg-color; display: flex; justify-content: center; align-items: center; } - &.mx_VideoFeed_video { + .mx_VideoFeed_video { + height: 100%; background-color: #000; } + + .mx_VideoFeed_mic { + left: 10px; + bottom: 10px; + } } } diff --git a/res/css/views/voip/_CallViewSidebar.scss b/res/css/views/voip/_CallViewSidebar.scss index 79bf3cbf098..892a137a320 100644 --- a/res/css/views/voip/_CallViewSidebar.scss +++ b/res/css/views/voip/_CallViewSidebar.scss @@ -35,12 +35,23 @@ limitations under the License. width: 100%; &.mx_VideoFeed_voice { + border-radius: 4px; + display: flex; align-items: center; justify-content: center; aspect-ratio: 16 / 9; } + + .mx_VideoFeed_video { + border-radius: 4px; + } + + .mx_VideoFeed_mic { + left: 6px; + bottom: 6px; + } } &.mx_CallViewSidebar_pipMode { diff --git a/res/css/views/voip/_VideoFeed.scss b/res/css/views/voip/_VideoFeed.scss index 07a4a0e5309..3a0f62636ed 100644 --- a/res/css/views/voip/_VideoFeed.scss +++ b/res/css/views/voip/_VideoFeed.scss @@ -15,18 +15,52 @@ limitations under the License. */ .mx_VideoFeed { - border-radius: 4px; - + overflow: hidden; + position: relative; &.mx_VideoFeed_voice { background-color: $inverted-bg-color; } - &.mx_VideoFeed_video { + .mx_VideoFeed_video { + width: 100%; background-color: transparent; + + &.mx_VideoFeed_video_mirror { + transform: scale(-1, 1); + } } -} -.mx_VideoFeed_mirror { - transform: scale(-1, 1); + .mx_VideoFeed_mic { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + + width: 24px; + height: 24px; + + background-color: rgba(0, 0, 0, 0.5); // Same on both themes + border-radius: 100%; + + &::before { + position: absolute; + content: ""; + width: 16px; + height: 16px; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + background-color: white; // Same on both themes + border-radius: 7px; + } + + &.mx_VideoFeed_mic_muted::before { + mask-image: url('$(res)/img/voip/mic-muted.svg'); + } + + &.mx_VideoFeed_mic_unmuted::before { + mask-image: url('$(res)/img/voip/mic-unmuted.svg'); + } + } } diff --git a/res/img/voip/mic-muted.svg b/res/img/voip/mic-muted.svg new file mode 100644 index 00000000000..0cb7ad1c9e5 --- /dev/null +++ b/res/img/voip/mic-muted.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/voip/mic-unmuted.svg b/res/img/voip/mic-unmuted.svg new file mode 100644 index 00000000000..8334cafa0ad --- /dev/null +++ b/res/img/voip/mic-unmuted.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx index 51d2adb8455..bff4559a6e2 100644 --- a/src/components/views/voip/VideoFeed.tsx +++ b/src/components/views/voip/VideoFeed.tsx @@ -22,6 +22,7 @@ import { CallFeed, CallFeedEvent } from 'matrix-js-sdk/src/webrtc/callFeed'; import { logger } from 'matrix-js-sdk/src/logger'; import MemberAvatar from "../avatars/MemberAvatar"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes'; interface IProps { call: MatrixCall; @@ -47,7 +48,7 @@ interface IState { } @replaceableComponent("views.voip.VideoFeed") -export default class VideoFeed extends React.Component { +export default class VideoFeed extends React.PureComponent { private element: HTMLVideoElement; constructor(props: IProps) { @@ -68,8 +69,15 @@ export default class VideoFeed extends React.Component { this.updateFeed(this.props.feed, null); } - componentDidUpdate(prevProps: IProps) { + componentDidUpdate(prevProps: IProps, prevState: IState) { this.updateFeed(prevProps.feed, this.props.feed); + // If the mutes state has changed, we try to playMedia() + if ( + prevState.videoMuted !== this.state.videoMuted || + prevProps.feed.stream !== this.props.feed.stream + ) { + this.playMedia(); + } } static getDerivedStateFromProps(props: IProps) { @@ -94,10 +102,12 @@ export default class VideoFeed extends React.Component { if (oldFeed) { this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream); + this.props.feed.removeListener(CallFeedEvent.MuteStateChanged, this.onMuteStateChanged); this.stopMedia(); } if (newFeed) { this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream); + this.props.feed.addListener(CallFeedEvent.MuteStateChanged, this.onMuteStateChanged); this.playMedia(); } } @@ -143,7 +153,13 @@ export default class VideoFeed extends React.Component { audioMuted: this.props.feed.isAudioMuted(), videoMuted: this.props.feed.isVideoMuted(), }); - this.playMedia(); + }; + + private onMuteStateChanged = () => { + this.setState({ + audioMuted: this.props.feed.isAudioMuted(), + videoMuted: this.props.feed.isVideoMuted(), + }); }; private onResize = (e) => { @@ -153,39 +169,58 @@ export default class VideoFeed extends React.Component { }; render() { - const videoClasses = { - mx_VideoFeed: true, + const { pipMode, primary, feed } = this.props; + + const wrapperClasses = classnames("mx_VideoFeed", { mx_VideoFeed_voice: this.state.videoMuted, - mx_VideoFeed_video: !this.state.videoMuted, - mx_VideoFeed_mirror: ( - this.props.feed.isLocal() && - SettingsStore.getValue('VideoView.flipVideoHorizontally') - ), - }; + }); + const micIconClasses = classnames("mx_VideoFeed_mic", { + mx_VideoFeed_mic_muted: this.state.audioMuted, + mx_VideoFeed_mic_unmuted: !this.state.audioMuted, + }); - const { pipMode, primary } = this.props; + let micIcon; + if (feed.purpose !== SDPStreamMetadataPurpose.Screenshare && !pipMode) { + micIcon = ( +
+ ); + } + let content; if (this.state.videoMuted) { const member = this.props.feed.getMember(); + let avatarSize; if (pipMode && primary) avatarSize = 76; else if (pipMode && !primary) avatarSize = 16; else if (!pipMode && primary) avatarSize = 160; else; // TBD - return ( -
- -
+ content =( + ); } else { - return ( -