Implement call hold
Currently just by adding /holdcall and /unholdcall slash commands The only place the hold status of the call is currently represented is when the call is a voice call and you're viewing a different room: it's not wired up when you're viewing the room because that currently uses the room status bar which it won't do with the new UI. Also convert VideoFeed to typescript, and remove videoview because it essentially just managed the fullscreen functionality, but we'll want and 'on hold' representation (and probably chrome for hagnup etc) in the fullscreen UI too, so let's just make CallView the thing that gets fullscreened.
This commit is contained in:
parent
ca4e7202ae
commit
f828c6d494
15 changed files with 287 additions and 330 deletions
|
@ -15,17 +15,18 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {createRef} from 'react';
|
||||
import React, { createRef } from 'react';
|
||||
import Room from 'matrix-js-sdk/src/models/room';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import CallHandler from '../../../CallHandler';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import VideoView from "./VideoView";
|
||||
import VideoFeed, { VideoFeedType } from "./VideoFeed";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import PulsedAvatar from '../avatars/PulsedAvatar';
|
||||
import { CallState, MatrixCall } from 'matrix-js-sdk/lib/webrtc/call';
|
||||
import { CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||
import { CallEvent } from 'matrix-js-sdk/src/webrtc/call';
|
||||
|
||||
interface IProps {
|
||||
// js-sdk room object. If set, we will only show calls for the given
|
||||
|
@ -50,53 +51,104 @@ interface IProps {
|
|||
}
|
||||
|
||||
interface IState {
|
||||
call: any;
|
||||
call: MatrixCall;
|
||||
isLocalOnHold: boolean,
|
||||
}
|
||||
|
||||
function getFullScreenElement() {
|
||||
return (
|
||||
document.fullscreenElement ||
|
||||
// moz omitted because firefox supports this unprefixed now (webkit here for safari)
|
||||
document.webkitFullscreenElement ||
|
||||
document.msFullscreenElement
|
||||
);
|
||||
}
|
||||
|
||||
function requestFullscreen(element: Element) {
|
||||
const method = (
|
||||
element.requestFullscreen ||
|
||||
// moz omitted since firefox supports unprefixed now
|
||||
element.webkitRequestFullScreen ||
|
||||
element.msRequestFullscreen
|
||||
);
|
||||
if (method) method.call(element);
|
||||
}
|
||||
|
||||
function exitFullscreen() {
|
||||
const exitMethod = (
|
||||
document.exitFullscreen ||
|
||||
document.webkitExitFullscreen ||
|
||||
document.msExitFullscreen
|
||||
);
|
||||
if (exitMethod) exitMethod.call(document);
|
||||
}
|
||||
|
||||
export default class CallView extends React.Component<IProps, IState> {
|
||||
private videoref: React.RefObject<any>;
|
||||
private dispatcherRef: string;
|
||||
public call: any;
|
||||
private container = createRef<HTMLDivElement>();
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
const call = this.getCall();
|
||||
this.state = {
|
||||
// the call this view is displaying (if any)
|
||||
call: null,
|
||||
};
|
||||
call,
|
||||
isLocalOnHold: call ? call.isLocalOnHold() : null,
|
||||
}
|
||||
|
||||
this.videoref = createRef();
|
||||
this.updateCallListeners(null, call);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
this.showCall();
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.updateCallListeners(this.state.call, null);
|
||||
dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
private onAction = (payload) => {
|
||||
// don't filter out payloads for room IDs other than props.room because
|
||||
// we may be interested in the conf 1:1 room
|
||||
if (payload.action !== 'call_state') {
|
||||
return;
|
||||
switch (payload.action) {
|
||||
case 'video_fullscreen': {
|
||||
if (!this.container.current) {
|
||||
return;
|
||||
}
|
||||
if (payload.fullscreen) {
|
||||
requestFullscreen(this.container.current);
|
||||
} else if (getFullScreenElement()) {
|
||||
exitFullscreen();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'call_state': {
|
||||
const newCall = this.getCall();
|
||||
if (newCall !== this.state.call) {
|
||||
this.updateCallListeners(this.state.call, newCall);
|
||||
this.setState({
|
||||
call: newCall,
|
||||
isLocalOnHold: newCall ? newCall.isLocalOnHold() : null,
|
||||
});
|
||||
}
|
||||
if (!newCall && getFullScreenElement()) {
|
||||
exitFullscreen();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.showCall();
|
||||
};
|
||||
|
||||
private showCall() {
|
||||
private getCall(): MatrixCall {
|
||||
let call: MatrixCall;
|
||||
|
||||
if (this.props.room) {
|
||||
const roomId = this.props.room.roomId;
|
||||
call = CallHandler.sharedInstance().getCallForRoom(roomId);
|
||||
|
||||
if (this.call) {
|
||||
this.setState({ call: call });
|
||||
}
|
||||
// We don't currently show voice calls in this view when in the room:
|
||||
// they're represented in the room status bar at the bottom instead
|
||||
// (but this will all change with the new designs)
|
||||
if (call && call.type == CallType.Voice) call = null;
|
||||
} else {
|
||||
call = CallHandler.sharedInstance().getAnyActiveCall();
|
||||
// Ignore calls if we can't get the room associated with them.
|
||||
|
@ -106,65 +158,68 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
if (MatrixClientPeg.get().getRoom(call.roomId) === null) {
|
||||
call = null;
|
||||
}
|
||||
this.setState({ call: call });
|
||||
}
|
||||
|
||||
if (call) {
|
||||
if (this.getVideoView()) {
|
||||
call.setLocalVideoElement(this.getVideoView().getLocalVideoElement());
|
||||
call.setRemoteVideoElement(this.getVideoView().getRemoteVideoElement());
|
||||
|
||||
// always use a separate element for audio stream playback.
|
||||
// this is to let us move CallView around the DOM without interrupting remote audio
|
||||
// during playback, by having the audio rendered by a top-level <audio/> element.
|
||||
// rather than being rendered by the main remoteVideo <video/> element.
|
||||
call.setRemoteAudioElement(this.getVideoView().getRemoteAudioElement());
|
||||
}
|
||||
}
|
||||
if (call && call.type === "video" && call.state !== CallState.Ended && call.state !== CallState.Ringing) {
|
||||
this.getVideoView().getLocalVideoElement().style.display = "block";
|
||||
this.getVideoView().getRemoteVideoElement().style.display = "block";
|
||||
} else {
|
||||
this.getVideoView().getLocalVideoElement().style.display = "none";
|
||||
this.getVideoView().getRemoteVideoElement().style.display = "none";
|
||||
dis.dispatch({action: 'video_fullscreen', fullscreen: false});
|
||||
}
|
||||
|
||||
if (this.props.onResize) {
|
||||
this.props.onResize();
|
||||
}
|
||||
if (call && call.state == CallState.Ended) return null;
|
||||
return call;
|
||||
}
|
||||
|
||||
private getVideoView() {
|
||||
return this.videoref.current;
|
||||
private updateCallListeners(oldCall: MatrixCall, newCall: MatrixCall) {
|
||||
if (oldCall === newCall) return;
|
||||
|
||||
if (oldCall) oldCall.removeListener(CallEvent.HoldUnhold, this.onCallHoldUnhold);
|
||||
if (newCall) newCall.on(CallEvent.HoldUnhold, this.onCallHoldUnhold);
|
||||
}
|
||||
|
||||
private onCallHoldUnhold = () => {
|
||||
this.setState({
|
||||
isLocalOnHold: this.state.call ? this.state.call.isLocalOnHold() : null,
|
||||
});
|
||||
};
|
||||
|
||||
public render() {
|
||||
let view: React.ReactNode;
|
||||
if (this.state.call && this.state.call.type === "voice") {
|
||||
const client = MatrixClientPeg.get();
|
||||
const callRoom = client.getRoom(this.state.call.roomId);
|
||||
|
||||
view = <AccessibleButton className="mx_CallView_voice" onClick={this.props.onClick}>
|
||||
<PulsedAvatar>
|
||||
<RoomAvatar
|
||||
room={callRoom}
|
||||
height={35}
|
||||
width={35}
|
||||
if (this.state.call) {
|
||||
if (this.state.call.type === "voice") {
|
||||
const client = MatrixClientPeg.get();
|
||||
const callRoom = client.getRoom(this.state.call.roomId);
|
||||
|
||||
let caption = _t("Active call");
|
||||
if (this.state.isLocalOnHold) {
|
||||
// we currently have no UI for holding / unholding a call (apart from slash
|
||||
// commands) so we don't disintguish between when we've put the call on hold
|
||||
// (ie. we'd show an unhold button) and when the other side has put us on hold
|
||||
// (where obviously we would not show such a button).
|
||||
caption = _t("Call Paused");
|
||||
}
|
||||
|
||||
view = <AccessibleButton className="mx_CallView_voice" onClick={this.props.onClick}>
|
||||
<PulsedAvatar>
|
||||
<RoomAvatar
|
||||
room={callRoom}
|
||||
height={35}
|
||||
width={35}
|
||||
/>
|
||||
</PulsedAvatar>
|
||||
<div>
|
||||
<h1>{callRoom.name}</h1>
|
||||
<p>{ caption }</p>
|
||||
</div>
|
||||
</AccessibleButton>;
|
||||
} else {
|
||||
// For video calls, we currently ignore the call hold state altogether
|
||||
// (the video will just go black)
|
||||
|
||||
// if we're fullscreen, we don't want to set a maxHeight on the video element.
|
||||
const maxVideoHeight = getFullScreenElement() ? null : this.props.maxVideoHeight;
|
||||
view = <div className="mx_CallView_video" onClick={this.props.onClick}>
|
||||
<VideoFeed type={VideoFeedType.Remote} call={this.state.call} onResize={this.props.onResize}
|
||||
maxHeight={maxVideoHeight}
|
||||
/>
|
||||
</PulsedAvatar>
|
||||
<div>
|
||||
<h1>{callRoom.name}</h1>
|
||||
<p>{ _t("Active call") }</p>
|
||||
</div>
|
||||
</AccessibleButton>;
|
||||
} else {
|
||||
view = <VideoView
|
||||
ref={this.videoref}
|
||||
onClick={this.props.onClick}
|
||||
onResize={this.props.onResize}
|
||||
maxHeight={this.props.maxVideoHeight}
|
||||
/>;
|
||||
<VideoFeed type={VideoFeedType.Local} call={this.state.call} />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
let hangup: React.ReactNode;
|
||||
|
@ -180,10 +235,9 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
/>;
|
||||
}
|
||||
|
||||
return <div className={this.props.className}>
|
||||
return <div className={this.props.className} ref={this.container}>
|
||||
{view}
|
||||
{hangup}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue