Prepare for Element Call integration (#9224)
* Improve accessibility and testability of Tooltip Adding a role to Tooltip was motivated by React Testing Library's reliance on accessibility-related attributes to locate elements. * Make the ReadyWatchingStore constructor safer The ReadyWatchingStore constructor previously had a chance to immediately call onReady, which was dangerous because it was potentially calling the derived class's onReady at a point when the derived class hadn't even finished construction yet. In normal usage, I guess this never was a problem, but it was causing some of the tests I was writing to crash. This is solved by separating out the onReady call into a start method. * Rename 1:1 call components to 'LegacyCall' to reflect the fact that they're slated for removal, and to not clash with the new Call code. * Refactor VideoChannelStore into Call and CallStore Call is an abstract class that currently only has a Jitsi implementation, but this will make it easy to later add an Element Call implementation. * Remove WidgetReady, ClientReady, and ForceHangupCall hacks These are no longer used by the new Jitsi call implementation, and can be removed. * yarn i18n * Delete call map entries instead of inserting nulls * Allow multiple active calls and consolidate call listeners * Fix a race condition when creating a video room * Un-hardcode the media device fallback labels * Apply misc code review fixes * yarn i18n * Disconnect from calls more politely on logout * Fix some strict mode errors * Fix another updateRoom race condition
This commit is contained in:
parent
50f6986f6c
commit
0d6a550c33
107 changed files with 2573 additions and 2157 deletions
|
@ -21,7 +21,7 @@ import classNames from "classnames";
|
|||
import dis from "../../dispatcher/dispatcher";
|
||||
import { _t } from "../../languageHandler";
|
||||
import RoomList from "../views/rooms/RoomList";
|
||||
import CallHandler from "../../CallHandler";
|
||||
import LegacyCallHandler from "../../LegacyCallHandler";
|
||||
import { HEADER_HEIGHT } from "../views/rooms/RoomSublist";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import RoomSearch from "./RoomSearch";
|
||||
|
@ -325,7 +325,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
|
||||
// If we have dialer support, show a button to bring up the dial pad
|
||||
// to start a new call
|
||||
if (CallHandler.instance.getSupportsPstnProtocol()) {
|
||||
if (LegacyCallHandler.instance.getSupportsPstnProtocol()) {
|
||||
dialPadButton =
|
||||
<AccessibleTooltipButton
|
||||
className={classNames("mx_LeftPanel_dialPadButton", {})}
|
||||
|
|
|
@ -19,10 +19,10 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
|||
import { CallEvent, CallState, CallType, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
|
||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from '../../LegacyCallHandler';
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
|
||||
export enum CallEventGrouperEvent {
|
||||
export enum LegacyCallEventGrouperEvent {
|
||||
StateChanged = "state_changed",
|
||||
SilencedChanged = "silenced_changed",
|
||||
LengthChanged = "length_changed",
|
||||
|
@ -44,10 +44,10 @@ export enum CustomCallState {
|
|||
Missed = "missed",
|
||||
}
|
||||
|
||||
export function buildCallEventGroupers(
|
||||
callEventGroupers: Map<string, CallEventGrouper>,
|
||||
export function buildLegacyCallEventGroupers(
|
||||
callEventGroupers: Map<string, LegacyCallEventGrouper>,
|
||||
events?: MatrixEvent[],
|
||||
): Map<string, CallEventGrouper> {
|
||||
): Map<string, LegacyCallEventGrouper> {
|
||||
const newCallEventGroupers = new Map();
|
||||
events?.forEach(ev => {
|
||||
if (!ev.getType().startsWith("m.call.") && !ev.getType().startsWith("org.matrix.call.")) {
|
||||
|
@ -57,10 +57,10 @@ export function buildCallEventGroupers(
|
|||
const callId = ev.getContent().call_id;
|
||||
if (!newCallEventGroupers.has(callId)) {
|
||||
if (callEventGroupers.has(callId)) {
|
||||
// reuse the CallEventGrouper object where possible
|
||||
// reuse the LegacyCallEventGrouper object where possible
|
||||
newCallEventGroupers.set(callId, callEventGroupers.get(callId));
|
||||
} else {
|
||||
newCallEventGroupers.set(callId, new CallEventGrouper());
|
||||
newCallEventGroupers.set(callId, new LegacyCallEventGrouper());
|
||||
}
|
||||
}
|
||||
newCallEventGroupers.get(callId).add(ev);
|
||||
|
@ -68,7 +68,7 @@ export function buildCallEventGroupers(
|
|||
return newCallEventGroupers;
|
||||
}
|
||||
|
||||
export default class CallEventGrouper extends EventEmitter {
|
||||
export default class LegacyCallEventGrouper extends EventEmitter {
|
||||
private events: Set<MatrixEvent> = new Set<MatrixEvent>();
|
||||
private call: MatrixCall;
|
||||
public state: CallState | CustomCallState;
|
||||
|
@ -76,8 +76,10 @@ export default class CallEventGrouper extends EventEmitter {
|
|||
constructor() {
|
||||
super();
|
||||
|
||||
CallHandler.instance.addListener(CallHandlerEvent.CallsChanged, this.setCall);
|
||||
CallHandler.instance.addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged);
|
||||
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallsChanged, this.setCall);
|
||||
LegacyCallHandler.instance.addListener(
|
||||
LegacyCallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged,
|
||||
);
|
||||
}
|
||||
|
||||
private get invite(): MatrixEvent {
|
||||
|
@ -138,31 +140,31 @@ export default class CallEventGrouper extends EventEmitter {
|
|||
}
|
||||
|
||||
private onSilencedCallsChanged = () => {
|
||||
const newState = CallHandler.instance.isCallSilenced(this.callId);
|
||||
this.emit(CallEventGrouperEvent.SilencedChanged, newState);
|
||||
const newState = LegacyCallHandler.instance.isCallSilenced(this.callId);
|
||||
this.emit(LegacyCallEventGrouperEvent.SilencedChanged, newState);
|
||||
};
|
||||
|
||||
private onLengthChanged = (length: number): void => {
|
||||
this.emit(CallEventGrouperEvent.LengthChanged, length);
|
||||
this.emit(LegacyCallEventGrouperEvent.LengthChanged, length);
|
||||
};
|
||||
|
||||
public answerCall = (): void => {
|
||||
CallHandler.instance.answerCall(this.roomId);
|
||||
LegacyCallHandler.instance.answerCall(this.roomId);
|
||||
};
|
||||
|
||||
public rejectCall = (): void => {
|
||||
CallHandler.instance.hangupOrReject(this.roomId, true);
|
||||
LegacyCallHandler.instance.hangupOrReject(this.roomId, true);
|
||||
};
|
||||
|
||||
public callBack = (): void => {
|
||||
CallHandler.instance.placeCall(this.roomId, this.isVoice ? CallType.Voice : CallType.Video);
|
||||
LegacyCallHandler.instance.placeCall(this.roomId, this.isVoice ? CallType.Voice : CallType.Video);
|
||||
};
|
||||
|
||||
public toggleSilenced = () => {
|
||||
const silenced = CallHandler.instance.isCallSilenced(this.callId);
|
||||
const silenced = LegacyCallHandler.instance.isCallSilenced(this.callId);
|
||||
silenced ?
|
||||
CallHandler.instance.unSilenceCall(this.callId) :
|
||||
CallHandler.instance.silenceCall(this.callId);
|
||||
LegacyCallHandler.instance.unSilenceCall(this.callId) :
|
||||
LegacyCallHandler.instance.silenceCall(this.callId);
|
||||
};
|
||||
|
||||
private setCallListeners() {
|
||||
|
@ -182,13 +184,13 @@ export default class CallEventGrouper extends EventEmitter {
|
|||
else if (this.hangup) this.state = CallState.Ended;
|
||||
else if (this.invite && this.call) this.state = CallState.Connecting;
|
||||
}
|
||||
this.emit(CallEventGrouperEvent.StateChanged, this.state);
|
||||
this.emit(LegacyCallEventGrouperEvent.StateChanged, this.state);
|
||||
};
|
||||
|
||||
private setCall = () => {
|
||||
if (this.call) return;
|
||||
|
||||
this.call = CallHandler.instance.getCallById(this.callId);
|
||||
this.call = LegacyCallHandler.instance.getCallById(this.callId);
|
||||
this.setCallListeners();
|
||||
this.setState();
|
||||
};
|
|
@ -51,8 +51,8 @@ import HostSignupContainer from '../views/host_signup/HostSignupContainer';
|
|||
import { getKeyBindingsManager } from '../../KeyBindingsManager';
|
||||
import { IOpts } from "../../createRoom";
|
||||
import SpacePanel from "../views/spaces/SpacePanel";
|
||||
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
|
||||
import AudioFeedArrayForCall from '../views/voip/AudioFeedArrayForCall';
|
||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from '../../LegacyCallHandler';
|
||||
import AudioFeedArrayForLegacyCall from '../views/voip/AudioFeedArrayForLegacyCall';
|
||||
import { OwnProfileStore } from '../../stores/OwnProfileStore';
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import RoomView from './RoomView';
|
||||
|
@ -146,7 +146,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
// use compact timeline view
|
||||
useCompactLayout: SettingsStore.getValue('useCompactLayout'),
|
||||
usageLimitDismissed: false,
|
||||
activeCalls: CallHandler.instance.getAllActiveCalls(),
|
||||
activeCalls: LegacyCallHandler.instance.getAllActiveCalls(),
|
||||
};
|
||||
|
||||
// stash the MatrixClient in case we log out before we are unmounted
|
||||
|
@ -163,7 +163,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
|
||||
componentDidMount() {
|
||||
document.addEventListener('keydown', this.onNativeKeyDown, false);
|
||||
CallHandler.instance.addListener(CallHandlerEvent.CallState, this.onCallState);
|
||||
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
|
||||
this.updateServerNoticeEvents();
|
||||
|
||||
|
@ -195,7 +195,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this.onNativeKeyDown, false);
|
||||
CallHandler.instance.removeListener(CallHandlerEvent.CallState, this.onCallState);
|
||||
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
this._matrixClient.removeListener(ClientEvent.AccountData, this.onAccountData);
|
||||
this._matrixClient.removeListener(ClientEvent.Sync, this.onSync);
|
||||
this._matrixClient.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||
|
@ -207,7 +207,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private onCallState = (): void => {
|
||||
const activeCalls = CallHandler.instance.getAllActiveCalls();
|
||||
const activeCalls = LegacyCallHandler.instance.getAllActiveCalls();
|
||||
if (activeCalls === this.state.activeCalls) return;
|
||||
this.setState({ activeCalls });
|
||||
};
|
||||
|
@ -658,7 +658,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
|
||||
const audioFeedArraysForCalls = this.state.activeCalls.map((call) => {
|
||||
return (
|
||||
<AudioFeedArrayForCall call={call} key={call.callId} />
|
||||
<AudioFeedArrayForLegacyCall call={call} key={call.callId} />
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ import { makeRoomPermalink } from "../../utils/permalinks/Permalinks";
|
|||
import { copyPlaintext } from "../../utils/strings";
|
||||
import { PosthogAnalytics } from '../../PosthogAnalytics';
|
||||
import { initSentry } from "../../sentry";
|
||||
import CallHandler from "../../CallHandler";
|
||||
import LegacyCallHandler from "../../LegacyCallHandler";
|
||||
import { showSpaceInvite } from "../../utils/space";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import { ActionPayload } from "../../dispatcher/payloads";
|
||||
|
@ -128,7 +128,7 @@ import { ViewStartChatOrReusePayload } from '../../dispatcher/payloads/ViewStart
|
|||
import { IConfigOptions } from "../../IConfigOptions";
|
||||
import { SnakedObject } from "../../utils/SnakedObject";
|
||||
import { leaveRoomBehaviour } from "../../utils/leave-behaviour";
|
||||
import VideoChannelStore from "../../stores/VideoChannelStore";
|
||||
import { CallStore } from "../../stores/CallStore";
|
||||
import { IRoomStateEventsActionPayload } from "../../actions/MatrixActionCreators";
|
||||
import { ShowThreadPayload } from "../../dispatcher/payloads/ShowThreadPayload";
|
||||
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
|
||||
|
@ -576,9 +576,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
break;
|
||||
case 'logout':
|
||||
CallHandler.instance.hangupAllCalls();
|
||||
if (VideoChannelStore.instance.connected) VideoChannelStore.instance.setDisconnected();
|
||||
Lifecycle.logout();
|
||||
LegacyCallHandler.instance.hangupAllCalls();
|
||||
Promise.all([...CallStore.instance.activeCalls].map(call => call.disconnect()))
|
||||
.finally(() => Lifecycle.logout());
|
||||
break;
|
||||
case 'require_registration':
|
||||
startAnyRegistrationFlow(payload as any);
|
||||
|
|
|
@ -40,7 +40,7 @@ import DMRoomMap from "../../utils/DMRoomMap";
|
|||
import NewRoomIntro from "../views/rooms/NewRoomIntro";
|
||||
import HistoryTile from "../views/rooms/HistoryTile";
|
||||
import defaultDispatcher from '../../dispatcher/dispatcher';
|
||||
import CallEventGrouper from "./CallEventGrouper";
|
||||
import LegacyCallEventGrouper from "./LegacyCallEventGrouper";
|
||||
import WhoIsTypingTile from '../views/rooms/WhoIsTypingTile';
|
||||
import ScrollPanel, { IScrollState } from "./ScrollPanel";
|
||||
import GenericEventListSummary from '../views/elements/GenericEventListSummary';
|
||||
|
@ -188,7 +188,7 @@ interface IProps {
|
|||
hideThreadedMessages?: boolean;
|
||||
disableGrouping?: boolean;
|
||||
|
||||
callEventGroupers: Map<string, CallEventGrouper>;
|
||||
callEventGroupers: Map<string, LegacyCallEventGrouper>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
|
|
@ -45,7 +45,7 @@ import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
|
|||
import ResizeNotifier from '../../utils/ResizeNotifier';
|
||||
import ContentMessages from '../../ContentMessages';
|
||||
import Modal from '../../Modal';
|
||||
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
|
||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from '../../LegacyCallHandler';
|
||||
import dis, { defaultDispatcher } from '../../dispatcher/dispatcher';
|
||||
import * as Rooms from '../../Rooms';
|
||||
import eventSearch, { searchPagination } from '../../Searching';
|
||||
|
@ -78,7 +78,7 @@ import EffectsOverlay from "../views/elements/EffectsOverlay";
|
|||
import { containsEmoji } from '../../effects/utils';
|
||||
import { CHAT_EFFECTS } from '../../effects';
|
||||
import WidgetStore from "../../stores/WidgetStore";
|
||||
import VideoRoomView from "./VideoRoomView";
|
||||
import { VideoRoomView } from "./VideoRoomView";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import Notifier from "../../Notifier";
|
||||
import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast";
|
||||
|
@ -810,7 +810,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
callState: callState,
|
||||
});
|
||||
|
||||
CallHandler.instance.on(CallHandlerEvent.CallState, this.onCallState);
|
||||
LegacyCallHandler.instance.on(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
window.addEventListener('beforeunload', this.onPageUnload);
|
||||
}
|
||||
|
||||
|
@ -847,7 +847,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
// (We could use isMounted, but facebook have deprecated that.)
|
||||
this.unmounted = true;
|
||||
|
||||
CallHandler.instance.removeListener(CallHandlerEvent.CallState, this.onCallState);
|
||||
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
|
||||
// update the scroll map before we get unmounted
|
||||
if (this.state.roomId) {
|
||||
|
@ -896,7 +896,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
);
|
||||
}
|
||||
|
||||
CallHandler.instance.off(CallHandlerEvent.CallState, this.onCallState);
|
||||
LegacyCallHandler.instance.off(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
|
||||
// cancel any pending calls to the throttled updated
|
||||
this.updateRoomMembers.cancel();
|
||||
|
@ -1655,7 +1655,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
}
|
||||
|
||||
private onCallPlaced = (type: CallType): void => {
|
||||
CallHandler.instance.placeCall(this.state.room?.roomId, type);
|
||||
LegacyCallHandler.instance.placeCall(this.state.room?.roomId, type);
|
||||
};
|
||||
|
||||
private onAppsClick = () => {
|
||||
|
@ -1872,7 +1872,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
if (!this.state.room) {
|
||||
return null;
|
||||
}
|
||||
return CallHandler.instance.getCallForRoom(this.state.room.roomId);
|
||||
return LegacyCallHandler.instance.getCallForRoom(this.state.room.roomId);
|
||||
}
|
||||
|
||||
// this has to be a proper method rather than an unnamed function,
|
||||
|
|
|
@ -52,7 +52,7 @@ import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
|
|||
import Spinner from "../views/elements/Spinner";
|
||||
import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
||||
import ErrorDialog from '../views/dialogs/ErrorDialog';
|
||||
import CallEventGrouper, { buildCallEventGroupers } from "./CallEventGrouper";
|
||||
import LegacyCallEventGrouper, { buildLegacyCallEventGroupers } from "./LegacyCallEventGrouper";
|
||||
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
|
@ -240,8 +240,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
private readReceiptActivityTimer: Timer;
|
||||
private readMarkerActivityTimer: Timer;
|
||||
|
||||
// A map of <callId, CallEventGrouper>
|
||||
private callEventGroupers = new Map<string, CallEventGrouper>();
|
||||
// A map of <callId, LegacyCallEventGrouper>
|
||||
private callEventGroupers = new Map<string, LegacyCallEventGrouper>();
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
@ -493,7 +493,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
this.timelineWindow.unpaginate(count, backwards);
|
||||
|
||||
const { events, liveEvents, firstVisibleEventIndex } = this.getEvents();
|
||||
this.buildCallEventGroupers(events);
|
||||
this.buildLegacyCallEventGroupers(events);
|
||||
const newState: Partial<IState> = {
|
||||
events,
|
||||
liveEvents,
|
||||
|
@ -555,7 +555,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
debuglog("paginate complete backwards:"+backwards+"; success:"+r);
|
||||
|
||||
const { events, liveEvents, firstVisibleEventIndex } = this.getEvents();
|
||||
this.buildCallEventGroupers(events);
|
||||
this.buildLegacyCallEventGroupers(events);
|
||||
const newState: Partial<IState> = {
|
||||
[paginatingKey]: false,
|
||||
[canPaginateKey]: r,
|
||||
|
@ -686,7 +686,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
if (this.unmounted) { return; }
|
||||
|
||||
const { events, liveEvents, firstVisibleEventIndex } = this.getEvents();
|
||||
this.buildCallEventGroupers(events);
|
||||
this.buildLegacyCallEventGroupers(events);
|
||||
const lastLiveEvent = liveEvents[liveEvents.length - 1];
|
||||
|
||||
const updatedState: Partial<IState> = {
|
||||
|
@ -855,7 +855,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
// TODO: We should restrict this to only events in our timeline,
|
||||
// but possibly the event tile itself should just update when this
|
||||
// happens to save us re-rendering the whole timeline.
|
||||
this.buildCallEventGroupers(this.state.events);
|
||||
this.buildLegacyCallEventGroupers(this.state.events);
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
|
@ -1405,7 +1405,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
onLoaded();
|
||||
} else {
|
||||
const prom = this.timelineWindow.load(eventId, INITIAL_SIZE);
|
||||
this.buildCallEventGroupers();
|
||||
this.buildLegacyCallEventGroupers();
|
||||
this.setState({
|
||||
events: [],
|
||||
liveEvents: [],
|
||||
|
@ -1426,7 +1426,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
if (this.unmounted) return;
|
||||
|
||||
const state = this.getEvents();
|
||||
this.buildCallEventGroupers(state.events);
|
||||
this.buildLegacyCallEventGroupers(state.events);
|
||||
this.setState(state);
|
||||
}
|
||||
|
||||
|
@ -1707,8 +1707,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
eventType: EventType | string,
|
||||
) => this.props.timelineSet.relations?.getChildEventsForEvent(eventId, relationType, eventType);
|
||||
|
||||
private buildCallEventGroupers(events?: MatrixEvent[]): void {
|
||||
this.callEventGroupers = buildCallEventGroupers(this.callEventGroupers, events);
|
||||
private buildLegacyCallEventGroupers(events?: MatrixEvent[]): void {
|
||||
this.callEventGroupers = buildLegacyCallEventGroupers(this.callEventGroupers, events);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -14,79 +14,46 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { FC, useContext, useState, useMemo, useEffect } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import React, { FC, useContext, useEffect } from "react";
|
||||
|
||||
import type { Room } from "matrix-js-sdk/src/models/room";
|
||||
import type { Call } from "../../models/Call";
|
||||
import { useCall, useConnectionState } from "../../hooks/useCall";
|
||||
import { isConnected } from "../../models/Call";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import { useEventEmitter } from "../../hooks/useEventEmitter";
|
||||
import WidgetUtils from "../../utils/WidgetUtils";
|
||||
import { addVideoChannel, getVideoChannel, fixStuckDevices } from "../../utils/VideoChannelUtils";
|
||||
import WidgetStore, { IApp } from "../../stores/WidgetStore";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import VideoChannelStore, { VideoChannelEvent } from "../../stores/VideoChannelStore";
|
||||
import AppTile from "../views/elements/AppTile";
|
||||
import VideoLobby from "../views/voip/VideoLobby";
|
||||
import { CallLobby } from "../views/voip/CallLobby";
|
||||
|
||||
interface IProps {
|
||||
interface Props {
|
||||
room: Room;
|
||||
resizing: boolean;
|
||||
}
|
||||
|
||||
const VideoRoomView: FC<IProps> = ({ room, resizing }) => {
|
||||
const LoadedVideoRoomView: FC<Props & { call: Call }> = ({ room, resizing, call }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const store = VideoChannelStore.instance;
|
||||
const connected = isConnected(useConnectionState(call));
|
||||
|
||||
// In case we mount before the WidgetStore knows about our Jitsi widget
|
||||
const [widgetStoreReady, setWidgetStoreReady] = useState(Boolean(WidgetStore.instance.matrixClient));
|
||||
const [widgetLoaded, setWidgetLoaded] = useState(false);
|
||||
useEventEmitter(WidgetStore.instance, UPDATE_EVENT, (roomId: string) => {
|
||||
if (roomId === null) setWidgetStoreReady(true);
|
||||
if (roomId === null || roomId === room.roomId) {
|
||||
setWidgetLoaded(Boolean(getVideoChannel(room.roomId)));
|
||||
}
|
||||
});
|
||||
// We'll take this opportunity to tidy up our room state
|
||||
useEffect(() => { call?.clean(); }, [call]);
|
||||
|
||||
const app: IApp = useMemo(() => {
|
||||
if (widgetStoreReady) {
|
||||
const app = getVideoChannel(room.roomId);
|
||||
if (!app) {
|
||||
logger.warn(`No video channel for room ${room.roomId}`);
|
||||
// Since widgets in video rooms are mutable, we'll take this opportunity to
|
||||
// reinstate the Jitsi widget in case another client removed it
|
||||
if (WidgetUtils.canUserModifyWidgets(room.roomId)) {
|
||||
addVideoChannel(room.roomId, room.name);
|
||||
}
|
||||
}
|
||||
return app;
|
||||
}
|
||||
}, [room, widgetStoreReady, widgetLoaded]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// We'll also take this opportunity to fix any stuck devices.
|
||||
// The linter thinks that store.connected should be a dependency, but we explicitly
|
||||
// *only* want this to happen at mount to avoid racing with normal device updates.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => { fixStuckDevices(room, store.connected); }, [room]);
|
||||
|
||||
const [connected, setConnected] = useState(store.connected && store.roomId === room.roomId);
|
||||
useEventEmitter(store, VideoChannelEvent.Connect, () => setConnected(store.roomId === room.roomId));
|
||||
useEventEmitter(store, VideoChannelEvent.Disconnect, () => setConnected(false));
|
||||
|
||||
if (!app) return null;
|
||||
if (!call) return null;
|
||||
|
||||
return <div className="mx_VideoRoomView">
|
||||
{ connected ? null : <VideoLobby room={room} /> }
|
||||
{ connected ? null : <CallLobby room={room} call={call} /> }
|
||||
{ /* We render the widget even if we're disconnected, so it stays loaded */ }
|
||||
<AppTile
|
||||
app={app}
|
||||
app={call.widget}
|
||||
room={room}
|
||||
userId={cli.credentials.userId}
|
||||
creatorUserId={app.creatorUserId}
|
||||
waitForIframeLoad={app.waitForIframeLoad}
|
||||
creatorUserId={call.widget.creatorUserId}
|
||||
waitForIframeLoad={call.widget.waitForIframeLoad}
|
||||
showMenubar={false}
|
||||
pointerEvents={resizing ? "none" : null}
|
||||
pointerEvents={resizing ? "none" : undefined}
|
||||
/>
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default VideoRoomView;
|
||||
export const VideoRoomView: FC<Props> = ({ room, resizing }) => {
|
||||
const call = useCall(room.roomId);
|
||||
return call ? <LoadedVideoRoomView room={room} resizing={resizing} call={call} /> : null;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue