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

Commit 3ea0571

Browse files
authored
Merge pull request #6486 from SimonBrandner/feature/muting
Show an avatar/a turned off microphone icon for muted users
2 parents a65b41b + 871f1b7 commit 3ea0571

File tree

6 files changed

+125
-30
lines changed

6 files changed

+125
-30
lines changed

res/css/views/voip/_CallView.scss

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,22 @@ limitations under the License.
7676

7777
&.mx_VideoFeed_voice {
7878
// We don't want to collide with the call controls that have 52px of height
79-
padding-bottom: 52px;
79+
margin-bottom: 52px;
8080
background-color: $inverted-bg-color;
8181
display: flex;
8282
justify-content: center;
8383
align-items: center;
8484
}
8585

86-
&.mx_VideoFeed_video {
86+
.mx_VideoFeed_video {
87+
height: 100%;
8788
background-color: #000;
8889
}
90+
91+
.mx_VideoFeed_mic {
92+
left: 10px;
93+
bottom: 10px;
94+
}
8995
}
9096
}
9197

res/css/views/voip/_CallViewSidebar.scss

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,23 @@ limitations under the License.
3535
width: 100%;
3636

3737
&.mx_VideoFeed_voice {
38+
border-radius: 4px;
39+
3840
display: flex;
3941
align-items: center;
4042
justify-content: center;
4143

4244
aspect-ratio: 16 / 9;
4345
}
46+
47+
.mx_VideoFeed_video {
48+
border-radius: 4px;
49+
}
50+
51+
.mx_VideoFeed_mic {
52+
left: 6px;
53+
bottom: 6px;
54+
}
4455
}
4556

4657
&.mx_CallViewSidebar_pipMode {

res/css/views/voip/_VideoFeed.scss

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,52 @@ limitations under the License.
1515
*/
1616

1717
.mx_VideoFeed {
18-
border-radius: 4px;
19-
18+
overflow: hidden;
19+
position: relative;
2020

2121
&.mx_VideoFeed_voice {
2222
background-color: $inverted-bg-color;
2323
}
2424

25-
&.mx_VideoFeed_video {
25+
.mx_VideoFeed_video {
26+
width: 100%;
2627
background-color: transparent;
28+
29+
&.mx_VideoFeed_video_mirror {
30+
transform: scale(-1, 1);
31+
}
2732
}
28-
}
2933

30-
.mx_VideoFeed_mirror {
31-
transform: scale(-1, 1);
34+
.mx_VideoFeed_mic {
35+
position: absolute;
36+
display: flex;
37+
align-items: center;
38+
justify-content: center;
39+
40+
width: 24px;
41+
height: 24px;
42+
43+
background-color: rgba(0, 0, 0, 0.5); // Same on both themes
44+
border-radius: 100%;
45+
46+
&::before {
47+
position: absolute;
48+
content: "";
49+
width: 16px;
50+
height: 16px;
51+
mask-repeat: no-repeat;
52+
mask-size: contain;
53+
mask-position: center;
54+
background-color: white; // Same on both themes
55+
border-radius: 7px;
56+
}
57+
58+
&.mx_VideoFeed_mic_muted::before {
59+
mask-image: url('$(res)/img/voip/mic-muted.svg');
60+
}
61+
62+
&.mx_VideoFeed_mic_unmuted::before {
63+
mask-image: url('$(res)/img/voip/mic-unmuted.svg');
64+
}
65+
}
3266
}

res/img/voip/mic-muted.svg

Lines changed: 5 additions & 0 deletions
Loading

res/img/voip/mic-unmuted.svg

Lines changed: 4 additions & 0 deletions
Loading

src/components/views/voip/VideoFeed.tsx

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { CallFeed, CallFeedEvent } from 'matrix-js-sdk/src/webrtc/callFeed';
2222
import { logger } from 'matrix-js-sdk/src/logger';
2323
import MemberAvatar from "../avatars/MemberAvatar";
2424
import { replaceableComponent } from "../../../utils/replaceableComponent";
25+
import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes';
2526

2627
interface IProps {
2728
call: MatrixCall;
@@ -47,7 +48,7 @@ interface IState {
4748
}
4849

4950
@replaceableComponent("views.voip.VideoFeed")
50-
export default class VideoFeed extends React.Component<IProps, IState> {
51+
export default class VideoFeed extends React.PureComponent<IProps, IState> {
5152
private element: HTMLVideoElement;
5253

5354
constructor(props: IProps) {
@@ -68,8 +69,15 @@ export default class VideoFeed extends React.Component<IProps, IState> {
6869
this.updateFeed(this.props.feed, null);
6970
}
7071

71-
componentDidUpdate(prevProps: IProps) {
72+
componentDidUpdate(prevProps: IProps, prevState: IState) {
7273
this.updateFeed(prevProps.feed, this.props.feed);
74+
// If the mutes state has changed, we try to playMedia()
75+
if (
76+
prevState.videoMuted !== this.state.videoMuted ||
77+
prevProps.feed.stream !== this.props.feed.stream
78+
) {
79+
this.playMedia();
80+
}
7381
}
7482

7583
static getDerivedStateFromProps(props: IProps) {
@@ -94,10 +102,12 @@ export default class VideoFeed extends React.Component<IProps, IState> {
94102

95103
if (oldFeed) {
96104
this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream);
105+
this.props.feed.removeListener(CallFeedEvent.MuteStateChanged, this.onMuteStateChanged);
97106
this.stopMedia();
98107
}
99108
if (newFeed) {
100109
this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream);
110+
this.props.feed.addListener(CallFeedEvent.MuteStateChanged, this.onMuteStateChanged);
101111
this.playMedia();
102112
}
103113
}
@@ -143,7 +153,13 @@ export default class VideoFeed extends React.Component<IProps, IState> {
143153
audioMuted: this.props.feed.isAudioMuted(),
144154
videoMuted: this.props.feed.isVideoMuted(),
145155
});
146-
this.playMedia();
156+
};
157+
158+
private onMuteStateChanged = () => {
159+
this.setState({
160+
audioMuted: this.props.feed.isAudioMuted(),
161+
videoMuted: this.props.feed.isVideoMuted(),
162+
});
147163
};
148164

149165
private onResize = (e) => {
@@ -153,39 +169,58 @@ export default class VideoFeed extends React.Component<IProps, IState> {
153169
};
154170

155171
render() {
156-
const videoClasses = {
157-
mx_VideoFeed: true,
172+
const { pipMode, primary, feed } = this.props;
173+
174+
const wrapperClasses = classnames("mx_VideoFeed", {
158175
mx_VideoFeed_voice: this.state.videoMuted,
159-
mx_VideoFeed_video: !this.state.videoMuted,
160-
mx_VideoFeed_mirror: (
161-
this.props.feed.isLocal() &&
162-
SettingsStore.getValue('VideoView.flipVideoHorizontally')
163-
),
164-
};
176+
});
177+
const micIconClasses = classnames("mx_VideoFeed_mic", {
178+
mx_VideoFeed_mic_muted: this.state.audioMuted,
179+
mx_VideoFeed_mic_unmuted: !this.state.audioMuted,
180+
});
165181

166-
const { pipMode, primary } = this.props;
182+
let micIcon;
183+
if (feed.purpose !== SDPStreamMetadataPurpose.Screenshare && !pipMode) {
184+
micIcon = (
185+
<div className={micIconClasses} />
186+
);
187+
}
167188

189+
let content;
168190
if (this.state.videoMuted) {
169191
const member = this.props.feed.getMember();
192+
170193
let avatarSize;
171194
if (pipMode && primary) avatarSize = 76;
172195
else if (pipMode && !primary) avatarSize = 16;
173196
else if (!pipMode && primary) avatarSize = 160;
174197
else; // TBD
175198

176-
return (
177-
<div className={classnames(videoClasses)}>
178-
<MemberAvatar
179-
member={member}
180-
height={avatarSize}
181-
width={avatarSize}
182-
/>
183-
</div>
199+
content =(
200+
<MemberAvatar
201+
member={member}
202+
height={avatarSize}
203+
width={avatarSize}
204+
/>
184205
);
185206
} else {
186-
return (
187-
<video className={classnames(videoClasses)} ref={this.setElementRef} />
207+
const videoClasses = classnames("mx_VideoFeed_video", {
208+
mx_VideoFeed_video_mirror: (
209+
this.props.feed.isLocal() &&
210+
SettingsStore.getValue('VideoView.flipVideoHorizontally')
211+
),
212+
});
213+
214+
content= (
215+
<video className={videoClasses} ref={this.setElementRef} />
188216
);
189217
}
218+
219+
return (
220+
<div className={wrapperClasses}>
221+
{ micIcon }
222+
{ content }
223+
</div>
224+
);
190225
}
191226
}

0 commit comments

Comments
 (0)