Add ability to switch between voice & video in calls (#7155)
This commit is contained in:
parent
756b924966
commit
b5d11336f7
5 changed files with 42 additions and 80 deletions
|
@ -21,10 +21,13 @@ limitations under the License.
|
|||
align-items: center;
|
||||
justify-content: left;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.mx_CallViewHeader_pip {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_CallViewHeader_callType {
|
||||
.mx_CallViewHeader_text {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
vertical-align: middle;
|
||||
|
@ -93,18 +96,7 @@ limitations under the License.
|
|||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.mx_CallViewHeader_callTypeSmall {
|
||||
font-size: 12px;
|
||||
color: $secondary-content;
|
||||
line-height: initial;
|
||||
height: 15px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.mx_CallViewHeader_callTypeIcon {
|
||||
.mx_CallViewHeader_icon {
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
height: 16px;
|
||||
|
@ -122,13 +114,6 @@ limitations under the License.
|
|||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
mask-position: center;
|
||||
}
|
||||
|
||||
&.mx_CallViewHeader_callTypeIcon_voice::before {
|
||||
mask-image: url('$(res)/img/element-icons/call/voice-call.svg');
|
||||
}
|
||||
|
||||
&.mx_CallViewHeader_callTypeIcon_video::before {
|
||||
mask-image: url('$(res)/img/element-icons/call/video-call.svg');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React, { createRef, CSSProperties } from 'react';
|
||||
import { CallEvent, CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||
import { CallEvent, CallState, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||
import classNames from 'classnames';
|
||||
import { CallFeed } from 'matrix-js-sdk/src/webrtc/callFeed';
|
||||
import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes';
|
||||
|
@ -339,35 +339,38 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private renderCallControls(): JSX.Element {
|
||||
// We don't support call upgrades (yet) so hide the video mute button in voice calls
|
||||
const vidMuteButtonShown = this.props.call.type === CallType.Video;
|
||||
const { call, pipMode } = this.props;
|
||||
const { primaryFeed, callState, micMuted, vidMuted, screensharing, sidebarShown } = this.state;
|
||||
|
||||
// If SDPStreamMetadata isn't supported don't show video mute button in voice calls
|
||||
const vidMuteButtonShown = call.opponentSupportsSDPStreamMetadata() || call.hasLocalUserMediaVideoTrack;
|
||||
// Screensharing is possible, if we can send a second stream and
|
||||
// identify it using SDPStreamMetadata or if we can replace the already
|
||||
// existing usermedia track by a screensharing track. We also need to be
|
||||
// connected to know the state of the other side
|
||||
const screensharingButtonShown = (
|
||||
(this.props.call.opponentSupportsSDPStreamMetadata() || this.props.call.type === CallType.Video) &&
|
||||
this.props.call.state === CallState.Connected
|
||||
(call.opponentSupportsSDPStreamMetadata() || call.hasLocalUserMediaVideoTrack) &&
|
||||
call.state === CallState.Connected
|
||||
);
|
||||
// To show the sidebar we need secondary feeds, if we don't have them,
|
||||
// we can hide this button. If we are in PiP, sidebar is also hidden, so
|
||||
// we can hide the button too
|
||||
const sidebarButtonShown = (
|
||||
this.state.primaryFeed?.purpose === SDPStreamMetadataPurpose.Screenshare ||
|
||||
this.props.call.isScreensharing()
|
||||
primaryFeed?.purpose === SDPStreamMetadataPurpose.Screenshare ||
|
||||
call.isScreensharing()
|
||||
);
|
||||
// The dial pad & 'more' button actions are only relevant in a connected call
|
||||
const contextMenuButtonShown = this.state.callState === CallState.Connected;
|
||||
const contextMenuButtonShown = callState === CallState.Connected;
|
||||
const dialpadButtonShown = (
|
||||
this.state.callState === CallState.Connected &&
|
||||
this.props.call.opponentSupportsDTMF()
|
||||
callState === CallState.Connected &&
|
||||
call.opponentSupportsDTMF()
|
||||
);
|
||||
|
||||
return (
|
||||
<CallViewButtons
|
||||
ref={this.buttonsRef}
|
||||
call={this.props.call}
|
||||
pipMode={this.props.pipMode}
|
||||
call={call}
|
||||
pipMode={pipMode}
|
||||
handlers={{
|
||||
onToggleSidebarClick: this.onToggleSidebar,
|
||||
onScreenshareClick: this.onScreenshareClick,
|
||||
|
@ -376,10 +379,10 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
onVidMuteClick: this.onVidMuteClick,
|
||||
}}
|
||||
buttonsState={{
|
||||
micMuted: this.state.micMuted,
|
||||
vidMuted: this.state.vidMuted,
|
||||
sidebarShown: this.state.sidebarShown,
|
||||
screensharing: this.state.screensharing,
|
||||
micMuted: micMuted,
|
||||
vidMuted: vidMuted,
|
||||
sidebarShown: sidebarShown,
|
||||
screensharing: screensharing,
|
||||
}}
|
||||
buttonsVisibility={{
|
||||
vidMute: vidMuteButtonShown,
|
||||
|
@ -406,7 +409,7 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
const someoneIsScreensharing = this.props.call.getFeeds().some((feed) => {
|
||||
return feed.purpose === SDPStreamMetadataPurpose.Screenshare;
|
||||
});
|
||||
const isVideoCall = this.props.call.type === CallType.Video;
|
||||
const call = this.props.call;
|
||||
|
||||
let contentView: React.ReactNode;
|
||||
let holdTransferContent;
|
||||
|
@ -461,7 +464,7 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
!isOnHold &&
|
||||
!transfereeCall &&
|
||||
sidebarShown &&
|
||||
(isVideoCall || someoneIsScreensharing)
|
||||
(call.hasLocalUserMediaVideoTrack || someoneIsScreensharing)
|
||||
) {
|
||||
sidebar = (
|
||||
<CallViewSidebar
|
||||
|
@ -474,7 +477,7 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
|
||||
// This is a bit messy. I can't see a reason to have two onHold/transfer screens
|
||||
if (isOnHold || transfereeCall) {
|
||||
if (isVideoCall) {
|
||||
if (call.hasLocalUserMediaVideoTrack || call.hasRemoteUserMediaVideoTrack) {
|
||||
const containerClasses = classNames({
|
||||
mx_CallView_content: true,
|
||||
mx_CallView_video: true,
|
||||
|
@ -569,7 +572,7 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
let text = isScreensharing
|
||||
? _t("You are presenting")
|
||||
: _t('%(sharerName)s is presenting', { sharerName });
|
||||
if (!this.state.sidebarShown && isVideoCall) {
|
||||
if (!this.state.sidebarShown) {
|
||||
text += " • " + (this.props.call.isLocalVideoMuted()
|
||||
? _t("Your camera is turned off")
|
||||
: _t("Your camera is still enabled"));
|
||||
|
@ -613,7 +616,6 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
<CallViewHeader
|
||||
onPipMouseDown={this.props.onMouseDownOnHeader}
|
||||
pipMode={this.props.pipMode}
|
||||
type={this.props.call.type}
|
||||
callRooms={[callRoom, secCallRoom]}
|
||||
/>
|
||||
{ contentView }
|
||||
|
|
|
@ -14,25 +14,17 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { CallType } from 'matrix-js-sdk/src/webrtc/call';
|
||||
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { _t, _td } from '../../../../languageHandler';
|
||||
import { _t } from '../../../../languageHandler';
|
||||
import RoomAvatar from '../../avatars/RoomAvatar';
|
||||
import dis from '../../../../dispatcher/dispatcher';
|
||||
import { Action } from '../../../../dispatcher/actions';
|
||||
import AccessibleTooltipButton from '../../elements/AccessibleTooltipButton';
|
||||
|
||||
const callTypeTranslationByType: Record<CallType, string> = {
|
||||
[CallType.Video]: _td("Video Call"),
|
||||
[CallType.Voice]: _td("Voice Call"),
|
||||
};
|
||||
|
||||
interface CallViewHeaderProps {
|
||||
pipMode: boolean;
|
||||
type?: CallType;
|
||||
callRooms?: Room[];
|
||||
onPipMouseDown: (event: React.MouseEvent<Element, MouseEvent>) => void;
|
||||
}
|
||||
|
@ -51,10 +43,10 @@ const onExpandClick = (roomId: string) => {
|
|||
});
|
||||
};
|
||||
|
||||
type CallControlsProps = Pick<CallViewHeaderProps, 'pipMode' | 'type'> & {
|
||||
type CallControlsProps = Pick<CallViewHeaderProps, 'pipMode'> & {
|
||||
roomId: string;
|
||||
};
|
||||
const CallViewHeaderControls: React.FC<CallControlsProps> = ({ pipMode = false, type, roomId }) => {
|
||||
const CallViewHeaderControls: React.FC<CallControlsProps> = ({ pipMode = false, roomId }) => {
|
||||
return <div className="mx_CallViewHeader_controls">
|
||||
{ !pipMode && <AccessibleTooltipButton
|
||||
className="mx_CallViewHeader_button mx_CallViewHeader_button_fullscreen"
|
||||
|
@ -77,47 +69,33 @@ const SecondaryCallInfo: React.FC<{ callRoom: Room }> = ({ callRoom }) => {
|
|||
</span>;
|
||||
};
|
||||
|
||||
const CallTypeIcon: React.FC<{ type: CallType }> = ({ type }) => {
|
||||
const classes = classNames({
|
||||
'mx_CallViewHeader_callTypeIcon': true,
|
||||
'mx_CallViewHeader_callTypeIcon_video': type === CallType.Video,
|
||||
'mx_CallViewHeader_callTypeIcon_voice': type === CallType.Voice,
|
||||
});
|
||||
return <div className={classes} />;
|
||||
};
|
||||
|
||||
const CallViewHeader: React.FC<CallViewHeaderProps> = ({
|
||||
type,
|
||||
pipMode = false,
|
||||
callRooms = [],
|
||||
onPipMouseDown,
|
||||
}) => {
|
||||
const [callRoom, onHoldCallRoom] = callRooms;
|
||||
const callTypeText = type ? _t(callTypeTranslationByType[type]) : _t("Widget");
|
||||
const callRoomName = callRoom?.name;
|
||||
const roomId = callRoom?.roomId;
|
||||
const callRoomName = callRoom.name;
|
||||
const { roomId } = callRoom;
|
||||
|
||||
if (!pipMode) {
|
||||
return <div className="mx_CallViewHeader">
|
||||
<CallTypeIcon type={type} />
|
||||
<span className="mx_CallViewHeader_callType">{ callTypeText }</span>
|
||||
<CallViewHeaderControls roomId={roomId} pipMode={pipMode} type={type} />
|
||||
<div className="mx_CallViewHeader_icon" />
|
||||
<span className="mx_CallViewHeader_text">{ _t("Call") }</span>
|
||||
<CallViewHeaderControls roomId={roomId} pipMode={pipMode} />
|
||||
</div>;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="mx_CallViewHeader"
|
||||
className="mx_CallViewHeader mx_CallViewHeader_pip"
|
||||
onMouseDown={onPipMouseDown}
|
||||
>
|
||||
<RoomAvatar room={callRoom} height={32} width={32} />
|
||||
<div className="mx_CallViewHeader_callInfo">
|
||||
<div className="mx_CallViewHeader_roomName" title={callRoomName}>{ callRoomName }</div>
|
||||
<div className="mx_CallViewHeader_callTypeSmall">
|
||||
{ callTypeText }
|
||||
<div className="mx_CallViewHeader_roomName">{ callRoomName }</div>
|
||||
{ onHoldCallRoom && <SecondaryCallInfo callRoom={onHoldCallRoom} /> }
|
||||
</div>
|
||||
</div>
|
||||
<CallViewHeaderControls roomId={roomId} pipMode={pipMode} type={type} />
|
||||
<CallViewHeaderControls roomId={roomId} pipMode={pipMode} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -303,7 +303,6 @@ export default class PipView extends React.Component<IProps, IState> {
|
|||
pipContent = ({ onStartMoving, _onResize }) =>
|
||||
<div className={pipViewClasses}>
|
||||
<CallViewHeader
|
||||
type={undefined}
|
||||
onPipMouseDown={(event) => { onStartMoving(event); this.onStartMoving.bind(this)(); }}
|
||||
pipMode={pipMode}
|
||||
callRooms={[roomForWidget]}
|
||||
|
|
|
@ -1012,12 +1012,10 @@
|
|||
"Show sidebar": "Show sidebar",
|
||||
"More": "More",
|
||||
"Hangup": "Hangup",
|
||||
"Video Call": "Video Call",
|
||||
"Voice Call": "Voice Call",
|
||||
"Fill Screen": "Fill Screen",
|
||||
"Return to call": "Return to call",
|
||||
"%(name)s on hold": "%(name)s on hold",
|
||||
"Widget": "Widget",
|
||||
"Call": "Call",
|
||||
"The other party cancelled the verification.": "The other party cancelled the verification.",
|
||||
"Verified!": "Verified!",
|
||||
"You've successfully verified this user.": "You've successfully verified this user.",
|
||||
|
|
Loading…
Reference in a new issue