Support hangup reasons

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner 2021-06-01 14:46:41 +02:00
parent 2a22f03a6a
commit 70a5715b3d
No known key found for this signature in database
GPG key ID: 9760693FDD98A790
3 changed files with 87 additions and 25 deletions

View file

@ -53,5 +53,9 @@ limitations under the License.
.mx_CallEvent_content_callBack {
margin-left: 10px; // To match mx_callEvent
}
.mx_CallEvent_content_tooltip {
margin-right: 5px;
}
}
}

View file

@ -82,6 +82,10 @@ export default class CallEventGrouper extends EventEmitter {
return this.state;
}
public getHangupReason(): string | null {
return this.events.find((event) => event.getType() === EventType.CallHangup)?.getContent()?.reason;
}
/**
* Returns true if there are only events from the other side - we missed the call
*/

View file

@ -22,6 +22,7 @@ import MemberAvatar from '../avatars/MemberAvatar';
import CallEventGrouper, { CallEventGrouperEvent, CustomCallState } from '../../structures/CallEventGrouper';
import FormButton from '../elements/FormButton';
import { CallState } from 'matrix-js-sdk/src/webrtc/call';
import InfoTooltip, { InfoTooltipKind } from '../elements/InfoTooltip';
interface IProps {
mxEvent: MatrixEvent;
@ -35,7 +36,6 @@ interface IState {
const TEXTUAL_STATES: Map<CallState | CustomCallState, string> = new Map([
[CallState.Connected, _td("Connected")],
[CallState.Connecting, _td("Connecting")],
[CallState.Ended, _td("This call has ended")],
]);
export default class CallEvent extends React.Component<IProps, IState> {
@ -59,53 +59,107 @@ export default class CallEvent extends React.Component<IProps, IState> {
this.setState({callState: newState});
}
render() {
const event = this.props.mxEvent;
const sender = event.sender ? event.sender.name : event.getSender();
const state = this.state.callState;
let content;
private renderContent(state: CallState | CustomCallState): JSX.Element {
if (state === CallState.Ringing) {
content = (
return (
<div className="mx_CallEvent_content">
<FormButton
onClick={this.props.callEventGrouper.rejectCall}
onClick={ this.props.callEventGrouper.rejectCall }
kind="danger"
label={_t("Decline")}
label={ _t("Decline") }
/>
<FormButton
onClick={this.props.callEventGrouper.answerCall}
onClick={ this.props.callEventGrouper.answerCall }
kind="primary"
label={_t("Accept")}
label={ _t("Accept") }
/>
</div>
);
} else if (Array.from(TEXTUAL_STATES.keys()).includes(state)) {
content = (
}
if (state === CallState.Ended) {
const hangupReason = this.props.callEventGrouper.getHangupReason();
if (["user_hangup", "user hangup"].includes(hangupReason) || !hangupReason) {
// workaround for https://github.com/vector-im/element-web/issues/5178
// it seems Android randomly sets a reason of "user hangup" which is
// interpreted as an error code :(
// https://github.com/vector-im/riot-android/issues/2623
// Also the correct hangup code as of VoIP v1 (with underscore)
// Also, if we don't have a reason
return (
<div className="mx_CallEvent_content">
{ _t("This call has ended") }
</div>
);
}
let reason;
if (hangupReason === "ice_failed") {
// We couldn't establish a connection at all
reason = _t("Could not connect media");
} else if (hangupReason === "ice_timeout") {
// We established a connection but it died
reason = _t("Connection failed");
} else if (hangupReason === "user_media_failed") {
// The other side couldn't open capture devices
reason = _t("Their device couldn't start the camera or microphone");
} else if (hangupReason === "unknown_error") {
// An error code the other side doesn't have a way to express
// (as opposed to an error code they gave but we don't know about,
// in which case we show the error code)
reason = _t("An unknown error occurred");
} else if (hangupReason === "invite_timeout") {
reason = _t("No answer");
} else {
reason = _t('Unknown failure: %(reason)s)', {reason: hangupReason});
}
return (
<div className="mx_CallEvent_content">
<InfoTooltip
tooltip={reason}
className="mx_CallEvent_content_tooltip"
kind={InfoTooltipKind.Warning}
/>
{ _t("This call has failed") }
</div>
);
}
if (Array.from(TEXTUAL_STATES.keys()).includes(state)) {
return (
<div className="mx_CallEvent_content">
{ TEXTUAL_STATES.get(state) }
</div>
);
} else if (state === CustomCallState.Missed) {
content = (
}
if (state === CustomCallState.Missed) {
return (
<div className="mx_CallEvent_content">
{ _t("You missed this call") }
<FormButton
className="mx_CallEvent_content_callBack"
onClick={this.props.callEventGrouper.callBack}
onClick={ this.props.callEventGrouper.callBack }
kind="primary"
label={_t("Call back")}
label={ _t("Call back") }
/>
</div>
);
} else {
content = (
<div className="mx_CallEvent_content">
{ "The call is in an unknown state!" }
</div>
);
}
// XXX: Should we translate this?
return (
<div className="mx_CallEvent_content">
{ "The call is in an unknown state!" }
</div>
);
}
render() {
const event = this.props.mxEvent;
const sender = event.sender ? event.sender.name : event.getSender();
const callType = this.props.callEventGrouper.isVoice() ? _t("Voice call") : _t("Video call");
const content = this.renderContent(this.state.callState);
return (
<div className="mx_CallEvent">
<div className="mx_CallEvent_info">
@ -119,7 +173,7 @@ export default class CallEvent extends React.Component<IProps, IState> {
{ sender }
</div>
<div className="mx_CallEvent_type">
{ this.props.callEventGrouper.isVoice() ? _t("Voice call") : _t("Video call") }
{ callType }
</div>
</div>
</div>