Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Show an avatar/a turned off microphone icon for muted users #6486

Merged
merged 12 commits into from
Aug 2, 2021
Merged
10 changes: 8 additions & 2 deletions res/css/views/voip/_CallView.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}

Expand Down
11 changes: 11 additions & 0 deletions res/css/views/voip/_CallViewSidebar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
46 changes: 40 additions & 6 deletions res/css/views/voip/_VideoFeed.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
}
}
5 changes: 5 additions & 0 deletions res/img/voip/mic-muted.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions res/img/voip/mic-unmuted.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 57 additions & 22 deletions src/components/views/voip/VideoFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -47,7 +48,7 @@ interface IState {
}

@replaceableComponent("views.voip.VideoFeed")
export default class VideoFeed extends React.Component<IProps, IState> {
export default class VideoFeed extends React.PureComponent<IProps, IState> {
private element: HTMLVideoElement;

constructor(props: IProps) {
Expand All @@ -68,8 +69,15 @@ export default class VideoFeed extends React.Component<IProps, IState> {
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) {
Expand All @@ -94,10 +102,12 @@ export default class VideoFeed extends React.Component<IProps, IState> {

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();
}
}
Expand Down Expand Up @@ -143,7 +153,13 @@ export default class VideoFeed extends React.Component<IProps, IState> {
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) => {
Expand All @@ -153,39 +169,58 @@ export default class VideoFeed extends React.Component<IProps, IState> {
};

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 = (
<div className={micIconClasses} />
);
}

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 (
<div className={classnames(videoClasses)}>
<MemberAvatar
member={member}
height={avatarSize}
width={avatarSize}
/>
</div>
content =(
<MemberAvatar
member={member}
height={avatarSize}
width={avatarSize}
/>
);
} else {
return (
<video className={classnames(videoClasses)} ref={this.setElementRef} />
const videoClasses = classnames("mx_VideoFeed_video", {
mx_VideoFeed_video_mirror: (
this.props.feed.isLocal() &&
SettingsStore.getValue('VideoView.flipVideoHorizontally')
),
});

content= (
<video className={videoClasses} ref={this.setElementRef} />
);
}

return (
<div className={wrapperClasses}>
{ micIcon }
{ content }
</div>
);
}
}