2020-07-07 00:42:46 +03:00
|
|
|
/*
|
|
|
|
Copyright 2017, 2018 New Vector Ltd
|
|
|
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2021-05-03 16:40:59 +03:00
|
|
|
import React, { createRef } from 'react';
|
2020-07-07 00:42:46 +03:00
|
|
|
|
2020-07-18 00:55:30 +03:00
|
|
|
import CallView from "./CallView";
|
2020-07-07 00:42:46 +03:00
|
|
|
import RoomViewStore from '../../../stores/RoomViewStore';
|
2021-04-28 12:49:07 +03:00
|
|
|
import CallHandler, { CallHandlerEvent } from '../../../CallHandler';
|
2020-07-07 00:42:46 +03:00
|
|
|
import dis from '../../../dispatcher/dispatcher';
|
|
|
|
import { ActionPayload } from '../../../dispatcher/payloads';
|
|
|
|
import PersistentApp from "../elements/PersistentApp";
|
|
|
|
import SettingsStore from "../../../settings/SettingsStore";
|
2020-12-03 20:45:49 +03:00
|
|
|
import { CallEvent, CallState, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
|
|
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
2021-03-09 06:20:07 +03:00
|
|
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
2021-06-17 16:52:55 +03:00
|
|
|
import UIStore from '../../../stores/UIStore';
|
2020-07-07 00:42:46 +03:00
|
|
|
|
2021-05-03 19:33:24 +03:00
|
|
|
const PIP_VIEW_WIDTH = 336;
|
|
|
|
const PIP_VIEW_HEIGHT = 232;
|
2021-05-03 19:11:02 +03:00
|
|
|
|
2021-05-03 19:33:24 +03:00
|
|
|
const DEFAULT_X_OFFSET = 16;
|
|
|
|
const DEFAULT_Y_OFFSET = 48;
|
2021-05-03 16:40:12 +03:00
|
|
|
|
2020-11-12 21:09:56 +03:00
|
|
|
const SHOW_CALL_IN_STATES = [
|
|
|
|
CallState.Connected,
|
|
|
|
CallState.InviteSent,
|
|
|
|
CallState.Connecting,
|
|
|
|
CallState.CreateAnswer,
|
|
|
|
CallState.CreateOffer,
|
|
|
|
CallState.WaitLocalMedia,
|
|
|
|
];
|
|
|
|
|
2020-07-07 00:42:46 +03:00
|
|
|
interface IProps {
|
|
|
|
}
|
|
|
|
|
|
|
|
interface IState {
|
|
|
|
roomId: string;
|
2020-12-03 20:45:49 +03:00
|
|
|
|
|
|
|
// The main call that we are displaying (ie. not including the call in the room being viewed, if any)
|
|
|
|
primaryCall: MatrixCall;
|
|
|
|
|
|
|
|
// Any other call we're displaying: only if the user is on two calls and not viewing either of the rooms
|
|
|
|
// they belong to
|
|
|
|
secondaryCall: MatrixCall;
|
2021-05-02 17:26:03 +03:00
|
|
|
|
|
|
|
// Position of the CallPreview
|
|
|
|
translationX: number;
|
|
|
|
translationY: number;
|
|
|
|
|
|
|
|
// True if the CallPreview is being dragged
|
|
|
|
moving: boolean;
|
2020-12-03 20:45:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Splits a list of calls into one 'primary' one and a list
|
|
|
|
// (which should be a single element) of other calls.
|
|
|
|
// The primary will be the one not on hold, or an arbitrary one
|
|
|
|
// if they're all on hold)
|
|
|
|
function getPrimarySecondaryCalls(calls: MatrixCall[]): [MatrixCall, MatrixCall[]] {
|
|
|
|
let primary: MatrixCall = null;
|
|
|
|
let secondaries: MatrixCall[] = [];
|
|
|
|
|
|
|
|
for (const call of calls) {
|
|
|
|
if (!SHOW_CALL_IN_STATES.includes(call.state)) continue;
|
|
|
|
|
|
|
|
if (!call.isRemoteOnHold() && primary === null) {
|
|
|
|
primary = call;
|
|
|
|
} else {
|
|
|
|
secondaries.push(call);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (primary === null && secondaries.length > 0) {
|
|
|
|
primary = secondaries[0];
|
|
|
|
secondaries = secondaries.slice(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (secondaries.length > 1) {
|
|
|
|
// We should never be in more than two calls so this shouldn't happen
|
|
|
|
console.log("Found more than 1 secondary call! Other calls will not be shown.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return [primary, secondaries];
|
2020-07-07 00:42:46 +03:00
|
|
|
}
|
|
|
|
|
2020-12-03 20:45:49 +03:00
|
|
|
/**
|
|
|
|
* CallPreview shows a small version of CallView hovering over the UI in 'picture-in-picture'
|
|
|
|
* (PiP mode). It displays the call(s) which is *not* in the room the user is currently viewing.
|
|
|
|
*/
|
2021-03-09 06:20:07 +03:00
|
|
|
@replaceableComponent("views.voip.CallPreview")
|
2020-07-07 00:42:46 +03:00
|
|
|
export default class CallPreview extends React.Component<IProps, IState> {
|
|
|
|
private roomStoreToken: any;
|
|
|
|
private dispatcherRef: string;
|
2020-07-07 17:11:08 +03:00
|
|
|
private settingsWatcherRef: string;
|
2020-07-07 00:42:46 +03:00
|
|
|
|
|
|
|
constructor(props: IProps) {
|
|
|
|
super(props);
|
|
|
|
|
2020-12-03 20:45:49 +03:00
|
|
|
const roomId = RoomViewStore.getRoomId();
|
|
|
|
|
|
|
|
const [primaryCall, secondaryCalls] = getPrimarySecondaryCalls(
|
|
|
|
CallHandler.sharedInstance().getAllActiveCallsNotInRoom(roomId),
|
|
|
|
);
|
|
|
|
|
2020-07-07 00:42:46 +03:00
|
|
|
this.state = {
|
2020-12-03 20:45:49 +03:00
|
|
|
roomId,
|
|
|
|
primaryCall: primaryCall,
|
|
|
|
secondaryCall: secondaryCalls[0],
|
2021-06-17 16:52:55 +03:00
|
|
|
translationX: UIStore.instance.windowWidth - DEFAULT_X_OFFSET - PIP_VIEW_WIDTH,
|
2021-05-03 16:40:12 +03:00
|
|
|
translationY: DEFAULT_Y_OFFSET,
|
2021-05-02 17:26:03 +03:00
|
|
|
moving: false,
|
2020-07-07 00:42:46 +03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-05-03 16:40:59 +03:00
|
|
|
private callViewWrapper = createRef<HTMLDivElement>();
|
|
|
|
|
2021-05-02 17:26:03 +03:00
|
|
|
private initX = 0;
|
|
|
|
private initY = 0;
|
|
|
|
|
2020-07-07 00:42:46 +03:00
|
|
|
public componentDidMount() {
|
2021-04-28 12:49:07 +03:00
|
|
|
CallHandler.sharedInstance().addListener(CallHandlerEvent.CallChangeRoom, this.updateCalls);
|
2020-07-07 00:42:46 +03:00
|
|
|
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
|
2021-05-02 21:57:18 +03:00
|
|
|
document.addEventListener("mousemove", this.onMoving);
|
2021-05-03 09:12:54 +03:00
|
|
|
document.addEventListener("mouseup", this.onEndMoving);
|
2021-05-03 19:11:02 +03:00
|
|
|
window.addEventListener("resize", this.onWindowSizeChanged);
|
2020-07-07 00:42:46 +03:00
|
|
|
this.dispatcherRef = dis.register(this.onAction);
|
2020-12-03 20:45:49 +03:00
|
|
|
MatrixClientPeg.get().on(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
|
2020-07-07 00:42:46 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
public componentWillUnmount() {
|
2021-04-28 12:49:07 +03:00
|
|
|
CallHandler.sharedInstance().removeListener(CallHandlerEvent.CallChangeRoom, this.updateCalls);
|
2020-12-03 20:45:49 +03:00
|
|
|
MatrixClientPeg.get().removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
|
2021-05-02 21:57:18 +03:00
|
|
|
document.removeEventListener("mousemove", this.onMoving);
|
2021-05-03 09:12:54 +03:00
|
|
|
document.removeEventListener("mouseup", this.onEndMoving);
|
2021-05-03 19:11:02 +03:00
|
|
|
window.removeEventListener("resize", this.onWindowSizeChanged);
|
2020-07-07 00:42:46 +03:00
|
|
|
if (this.roomStoreToken) {
|
|
|
|
this.roomStoreToken.remove();
|
|
|
|
}
|
|
|
|
dis.unregister(this.dispatcherRef);
|
2020-07-07 17:40:05 +03:00
|
|
|
SettingsStore.unwatchSetting(this.settingsWatcherRef);
|
2020-07-07 00:42:46 +03:00
|
|
|
}
|
|
|
|
|
2021-05-03 19:11:02 +03:00
|
|
|
private onWindowSizeChanged = () => {
|
2021-05-03 19:16:36 +03:00
|
|
|
this.setTranslation(this.state.translationX, this.state.translationY);
|
2021-05-03 19:36:11 +03:00
|
|
|
};
|
2021-05-03 19:16:36 +03:00
|
|
|
|
|
|
|
private setTranslation(inTranslationX: number, inTranslationY: number) {
|
2021-05-03 20:59:51 +03:00
|
|
|
const width = this.callViewWrapper.current?.clientWidth || PIP_VIEW_WIDTH;
|
|
|
|
const height = this.callViewWrapper.current?.clientHeight || PIP_VIEW_HEIGHT;
|
2021-05-03 19:11:02 +03:00
|
|
|
|
2021-05-03 19:16:36 +03:00
|
|
|
let outTranslationX;
|
|
|
|
let outTranslationY;
|
2021-05-03 19:11:02 +03:00
|
|
|
|
|
|
|
// Avoid overflow on the x axis
|
2021-06-17 16:52:55 +03:00
|
|
|
if (inTranslationX + width >= UIStore.instance.windowWidth) {
|
|
|
|
outTranslationX = UIStore.instance.windowWidth - width;
|
2021-05-03 19:16:36 +03:00
|
|
|
} else if (inTranslationX <= 0) {
|
|
|
|
outTranslationX = 0;
|
2021-05-03 19:11:02 +03:00
|
|
|
} else {
|
2021-05-03 19:16:36 +03:00
|
|
|
outTranslationX = inTranslationX;
|
2021-05-03 19:11:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Avoid overflow on the y axis
|
2021-06-17 16:52:55 +03:00
|
|
|
if (inTranslationY + height >= UIStore.instance.windowHeight) {
|
|
|
|
outTranslationY = UIStore.instance.windowHeight - height;
|
2021-05-03 19:16:36 +03:00
|
|
|
} else if (inTranslationY <= 0) {
|
|
|
|
outTranslationY = 0;
|
2021-05-03 19:11:02 +03:00
|
|
|
} else {
|
2021-05-03 19:16:36 +03:00
|
|
|
outTranslationY = inTranslationY;
|
2021-05-03 19:11:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({
|
2021-05-03 19:16:36 +03:00
|
|
|
translationX: outTranslationX,
|
|
|
|
translationY: outTranslationY,
|
2021-05-03 19:11:02 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-07-07 00:42:46 +03:00
|
|
|
private onRoomViewStoreUpdate = (payload) => {
|
|
|
|
if (RoomViewStore.getRoomId() === this.state.roomId) return;
|
2020-12-03 20:45:49 +03:00
|
|
|
|
|
|
|
const roomId = RoomViewStore.getRoomId();
|
|
|
|
const [primaryCall, secondaryCalls] = getPrimarySecondaryCalls(
|
|
|
|
CallHandler.sharedInstance().getAllActiveCallsNotInRoom(roomId),
|
|
|
|
);
|
|
|
|
|
2020-07-07 00:42:46 +03:00
|
|
|
this.setState({
|
2020-12-03 20:45:49 +03:00
|
|
|
roomId,
|
|
|
|
primaryCall: primaryCall,
|
|
|
|
secondaryCall: secondaryCalls[0],
|
2020-07-07 00:42:46 +03:00
|
|
|
});
|
2020-07-07 17:40:05 +03:00
|
|
|
};
|
2020-07-07 00:42:46 +03:00
|
|
|
|
|
|
|
private onAction = (payload: ActionPayload) => {
|
|
|
|
switch (payload.action) {
|
|
|
|
// listen for call state changes to prod the render method, which
|
|
|
|
// may hide the global CallView if the call it is tracking is dead
|
2020-12-03 20:45:49 +03:00
|
|
|
case 'call_state': {
|
2021-04-28 12:49:07 +03:00
|
|
|
this.updateCalls();
|
2020-07-07 00:42:46 +03:00
|
|
|
break;
|
2020-12-03 20:45:49 +03:00
|
|
|
}
|
2020-07-07 00:42:46 +03:00
|
|
|
}
|
2020-07-07 17:40:05 +03:00
|
|
|
};
|
2020-07-07 00:42:46 +03:00
|
|
|
|
2021-04-28 12:49:07 +03:00
|
|
|
private updateCalls = () => {
|
|
|
|
const [primaryCall, secondaryCalls] = getPrimarySecondaryCalls(
|
|
|
|
CallHandler.sharedInstance().getAllActiveCallsNotInRoom(this.state.roomId),
|
|
|
|
);
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
primaryCall: primaryCall,
|
|
|
|
secondaryCall: secondaryCalls[0],
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2020-12-03 20:45:49 +03:00
|
|
|
private onCallRemoteHold = () => {
|
|
|
|
const [primaryCall, secondaryCalls] = getPrimarySecondaryCalls(
|
|
|
|
CallHandler.sharedInstance().getAllActiveCallsNotInRoom(this.state.roomId),
|
2020-07-17 23:02:51 +03:00
|
|
|
);
|
|
|
|
|
2020-12-03 20:45:49 +03:00
|
|
|
this.setState({
|
|
|
|
primaryCall: primaryCall,
|
|
|
|
secondaryCall: secondaryCalls[0],
|
|
|
|
});
|
2021-05-03 16:43:13 +03:00
|
|
|
};
|
2020-12-03 20:45:49 +03:00
|
|
|
|
2021-05-02 17:26:41 +03:00
|
|
|
private onStartMoving = (event: React.MouseEvent) => {
|
2021-05-02 22:17:59 +03:00
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
|
|
|
|
2021-05-02 17:26:41 +03:00
|
|
|
this.setState({moving: true});
|
|
|
|
|
2021-05-03 18:49:55 +03:00
|
|
|
this.initX = event.pageX - this.state.translationX;
|
|
|
|
this.initY = event.pageY - this.state.translationY;
|
2021-05-03 16:43:13 +03:00
|
|
|
};
|
2021-05-02 17:26:41 +03:00
|
|
|
|
2021-05-02 21:57:18 +03:00
|
|
|
private onMoving = (event: React.MouseEvent | MouseEvent) => {
|
2021-05-03 16:07:25 +03:00
|
|
|
if (!this.state.moving) return;
|
|
|
|
|
2021-05-02 22:17:59 +03:00
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
|
|
|
|
2021-05-03 19:16:36 +03:00
|
|
|
this.setTranslation(event.pageX - this.initX, event.pageY - this.initY);
|
2021-05-03 16:43:13 +03:00
|
|
|
};
|
2021-05-02 17:26:41 +03:00
|
|
|
|
|
|
|
private onEndMoving = () => {
|
|
|
|
this.setState({moving: false});
|
2021-05-03 16:43:13 +03:00
|
|
|
};
|
2021-05-02 17:26:41 +03:00
|
|
|
|
2020-12-03 20:45:49 +03:00
|
|
|
public render() {
|
|
|
|
if (this.state.primaryCall) {
|
2021-05-02 17:26:54 +03:00
|
|
|
const translatePixelsX = this.state.translationX + "px";
|
|
|
|
const translatePixelsY = this.state.translationY + "px";
|
|
|
|
const style = {
|
|
|
|
transform: `translateX(${translatePixelsX})
|
|
|
|
translateY(${translatePixelsY})`,
|
|
|
|
};
|
|
|
|
|
2020-07-17 23:02:51 +03:00
|
|
|
return (
|
2021-05-03 16:40:59 +03:00
|
|
|
<div
|
|
|
|
className="mx_CallPreview"
|
|
|
|
style={style}
|
|
|
|
ref={this.callViewWrapper}
|
|
|
|
>
|
2021-05-02 17:26:54 +03:00
|
|
|
<CallView
|
|
|
|
call={this.state.primaryCall}
|
|
|
|
secondaryCall={this.state.secondaryCall}
|
|
|
|
pipMode={true}
|
2021-05-03 16:16:08 +03:00
|
|
|
onMouseDownOnHeader={this.onStartMoving}
|
2021-05-02 17:26:54 +03:00
|
|
|
/>
|
|
|
|
</div>
|
2020-07-07 00:42:46 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-07-17 23:02:51 +03:00
|
|
|
return <PersistentApp />;
|
2020-07-07 00:42:46 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|