Proactively fix stuck devices in video rooms (#8587)

* Proactively fix stuck devices in video rooms

* Fix tests

* Explain why we're disabling the lint rule

* Apply code review suggestions

* Back VideoChannelStore's flags by SettingsStore instead of localStorage
This commit is contained in:
Robin 2022-05-16 16:54:08 -04:00 committed by GitHub
parent 6f851108be
commit ceda77d7dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 149 additions and 49 deletions

View file

@ -18,15 +18,13 @@ import EventEmitter from "events";
import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
import { ClientWidgetApi, IWidgetApiRequest } from "matrix-widget-api";
import SettingsStore from "../settings/SettingsStore";
import { SettingLevel } from "../settings/SettingLevel";
import defaultDispatcher from "../dispatcher/dispatcher";
import { ActionPayload } from "../dispatcher/payloads";
import { ElementWidgetActions } from "./widgets/ElementWidgetActions";
import { WidgetMessagingStore, WidgetMessagingStoreEvent } from "./widgets/WidgetMessagingStore";
import {
VIDEO_CHANNEL_MEMBER,
IVideoChannelMemberContent,
getVideoChannel,
} from "../utils/VideoChannelUtils";
import { getVideoChannel, addOurDevice, removeOurDevice } from "../utils/VideoChannelUtils";
import { timeout } from "../utils/promise";
import WidgetUtils from "../utils/WidgetUtils";
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
@ -83,9 +81,13 @@ export default class VideoChannelStore extends AsyncStoreWithClient<null> {
private activeChannel: ClientWidgetApi;
private _roomId: string;
public get roomId(): string { return this._roomId; }
private set roomId(value: string) { this._roomId = value; }
// This is persisted to settings so we can detect unclean disconnects
public get roomId(): string | null { return SettingsStore.getValue("videoChannelRoomId"); }
private set roomId(value: string | null) {
SettingsStore.setValue("videoChannelRoomId", null, SettingLevel.DEVICE, value);
}
private get room(): Room { return this.matrixClient.getRoom(this.roomId); }
private _connected = false;
public get connected(): boolean { return this._connected; }
@ -95,18 +97,14 @@ export default class VideoChannelStore extends AsyncStoreWithClient<null> {
public get participants(): IJitsiParticipant[] { return this._participants; }
private set participants(value: IJitsiParticipant[]) { this._participants = value; }
private _audioMuted = localStorage.getItem("mx_audioMuted") === "true";
public get audioMuted(): boolean { return this._audioMuted; }
public get audioMuted(): boolean { return SettingsStore.getValue("audioInputMuted"); }
public set audioMuted(value: boolean) {
this._audioMuted = value;
localStorage.setItem("mx_audioMuted", value.toString());
SettingsStore.setValue("audioInputMuted", null, SettingLevel.DEVICE, value);
}
private _videoMuted = localStorage.getItem("mx_videoMuted") === "true";
public get videoMuted(): boolean { return this._videoMuted; }
public get videoMuted(): boolean { return SettingsStore.getValue("videoInputMuted"); }
public set videoMuted(value: boolean) {
this._videoMuted = value;
localStorage.setItem("mx_videoMuted", value.toString());
SettingsStore.setValue("videoInputMuted", null, SettingLevel.DEVICE, value);
}
public connect = async (roomId: string, audioDevice: MediaDeviceInfo, videoDevice: MediaDeviceInfo) => {
@ -198,13 +196,13 @@ export default class VideoChannelStore extends AsyncStoreWithClient<null> {
}
this.connected = true;
this.matrixClient.getRoom(roomId).on(RoomEvent.MyMembership, this.onMyMembership);
this.room.on(RoomEvent.MyMembership, this.onMyMembership);
window.addEventListener("beforeunload", this.setDisconnected);
this.emit(VideoChannelEvent.Connect, roomId);
// Tell others that we're connected, by adding our device to room state
this.updateDevices(roomId, devices => Array.from(new Set(devices).add(this.matrixClient.getDeviceId())));
await addOurDevice(this.room);
};
public disconnect = async () => {
@ -221,10 +219,11 @@ export default class VideoChannelStore extends AsyncStoreWithClient<null> {
public setDisconnected = async () => {
const roomId = this.roomId;
const room = this.room;
this.activeChannel.off(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
this.activeChannel.off(`action:${ElementWidgetActions.CallParticipants}`, this.onParticipants);
this.matrixClient.getRoom(roomId).off(RoomEvent.MyMembership, this.onMyMembership);
room.off(RoomEvent.MyMembership, this.onMyMembership);
window.removeEventListener("beforeunload", this.setDisconnected);
this.activeChannel = null;
@ -235,11 +234,7 @@ export default class VideoChannelStore extends AsyncStoreWithClient<null> {
this.emit(VideoChannelEvent.Disconnect, roomId);
// Tell others that we're disconnected, by removing our device from room state
await this.updateDevices(roomId, devices => {
const devicesSet = new Set(devices);
devicesSet.delete(this.matrixClient.getDeviceId());
return Array.from(devicesSet);
});
await removeOurDevice(room);
};
private ack = (ev: CustomEvent<IWidgetApiRequest>) => {
@ -248,18 +243,6 @@ export default class VideoChannelStore extends AsyncStoreWithClient<null> {
this.activeChannel.transport.reply(ev.detail, {});
};
private updateDevices = async (roomId: string, fn: (devices: string[]) => string[]) => {
const room = this.matrixClient.getRoom(roomId);
if (room.getMyMembership() !== "join") return;
const devicesState = room.currentState.getStateEvents(VIDEO_CHANNEL_MEMBER, this.matrixClient.getUserId());
const devices = devicesState?.getContent<IVideoChannelMemberContent>()?.devices ?? [];
await this.matrixClient.sendStateEvent(
roomId, VIDEO_CHANNEL_MEMBER, { devices: fn(devices) }, this.matrixClient.getUserId(),
);
};
private onHangup = async (ev: CustomEvent<IWidgetApiRequest>) => {
this.ack(ev);
// In case this hangup is caused by Jitsi Meet crashing at startup,