Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
);
}
}