0d6a550c33
* 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
154 lines
5.9 KiB
TypeScript
154 lines
5.9 KiB
TypeScript
/*
|
|
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
|
|
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
|
|
|
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.
|
|
*/
|
|
|
|
import EventEmitter from 'events';
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
|
|
|
import SettingsStore from "./settings/SettingsStore";
|
|
import { SettingLevel } from "./settings/SettingLevel";
|
|
import { MatrixClientPeg } from "./MatrixClientPeg";
|
|
|
|
// XXX: MediaDeviceKind is a union type, so we make our own enum
|
|
export enum MediaDeviceKindEnum {
|
|
AudioOutput = "audiooutput",
|
|
AudioInput = "audioinput",
|
|
VideoInput = "videoinput",
|
|
}
|
|
|
|
export type IMediaDevices = Record<MediaDeviceKindEnum, Array<MediaDeviceInfo>>;
|
|
|
|
export enum MediaDeviceHandlerEvent {
|
|
AudioOutputChanged = "audio_output_changed",
|
|
}
|
|
|
|
export default class MediaDeviceHandler extends EventEmitter {
|
|
private static internalInstance;
|
|
|
|
public static get instance(): MediaDeviceHandler {
|
|
if (!MediaDeviceHandler.internalInstance) {
|
|
MediaDeviceHandler.internalInstance = new MediaDeviceHandler();
|
|
}
|
|
return MediaDeviceHandler.internalInstance;
|
|
}
|
|
|
|
public static async hasAnyLabeledDevices(): Promise<boolean> {
|
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
return devices.some(d => Boolean(d.label));
|
|
}
|
|
|
|
public static async getDevices(): Promise<IMediaDevices> {
|
|
// Only needed for Electron atm, though should work in modern browsers
|
|
// once permission has been granted to the webapp
|
|
|
|
try {
|
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
const output = {
|
|
[MediaDeviceKindEnum.AudioOutput]: [],
|
|
[MediaDeviceKindEnum.AudioInput]: [],
|
|
[MediaDeviceKindEnum.VideoInput]: [],
|
|
};
|
|
|
|
devices.forEach((device) => output[device.kind].push(device));
|
|
return output;
|
|
} catch (error) {
|
|
logger.warn('Unable to refresh WebRTC Devices: ', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves devices from the SettingsStore and tells the js-sdk to use them
|
|
*/
|
|
public static async loadDevices(): Promise<void> {
|
|
const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
|
|
const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
|
|
|
|
await MatrixClientPeg.get().getMediaHandler().setAudioInput(audioDeviceId);
|
|
await MatrixClientPeg.get().getMediaHandler().setVideoInput(videoDeviceId);
|
|
}
|
|
|
|
public setAudioOutput(deviceId: string): void {
|
|
SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
|
|
this.emit(MediaDeviceHandlerEvent.AudioOutputChanged, deviceId);
|
|
}
|
|
|
|
/**
|
|
* This will not change the device that a potential call uses. The call will
|
|
* need to be ended and started again for this change to take effect
|
|
* @param {string} deviceId
|
|
*/
|
|
public async setAudioInput(deviceId: string): Promise<void> {
|
|
SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId);
|
|
return MatrixClientPeg.get().getMediaHandler().setAudioInput(deviceId);
|
|
}
|
|
|
|
/**
|
|
* This will not change the device that a potential call uses. The call will
|
|
* need to be ended and started again for this change to take effect
|
|
* @param {string} deviceId
|
|
*/
|
|
public async setVideoInput(deviceId: string): Promise<void> {
|
|
SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId);
|
|
return MatrixClientPeg.get().getMediaHandler().setVideoInput(deviceId);
|
|
}
|
|
|
|
public async setDevice(deviceId: string, kind: MediaDeviceKindEnum): Promise<void> {
|
|
switch (kind) {
|
|
case MediaDeviceKindEnum.AudioOutput: this.setAudioOutput(deviceId); break;
|
|
case MediaDeviceKindEnum.AudioInput: await this.setAudioInput(deviceId); break;
|
|
case MediaDeviceKindEnum.VideoInput: await this.setVideoInput(deviceId); break;
|
|
}
|
|
}
|
|
|
|
public static getAudioOutput(): string {
|
|
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audiooutput");
|
|
}
|
|
|
|
public static getAudioInput(): string {
|
|
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audioinput");
|
|
}
|
|
|
|
public static getVideoInput(): string {
|
|
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_videoinput");
|
|
}
|
|
|
|
/**
|
|
* Returns the current set deviceId for a device kind
|
|
* @param {MediaDeviceKindEnum} kind of the device that will be returned
|
|
* @returns {string} the deviceId
|
|
*/
|
|
public static getDevice(kind: MediaDeviceKindEnum): string {
|
|
switch (kind) {
|
|
case MediaDeviceKindEnum.AudioOutput: return this.getAudioOutput();
|
|
case MediaDeviceKindEnum.AudioInput: return this.getAudioInput();
|
|
case MediaDeviceKindEnum.VideoInput: return this.getVideoInput();
|
|
}
|
|
}
|
|
|
|
public static get startWithAudioMuted(): boolean {
|
|
return SettingsStore.getValue("audioInputMuted");
|
|
}
|
|
public static set startWithAudioMuted(value: boolean) {
|
|
SettingsStore.setValue("audioInputMuted", null, SettingLevel.DEVICE, value);
|
|
}
|
|
|
|
public static get startWithVideoMuted(): boolean {
|
|
return SettingsStore.getValue("videoInputMuted");
|
|
}
|
|
public static set startWithVideoMuted(value: boolean) {
|
|
SettingsStore.setValue("videoInputMuted", null, SettingLevel.DEVICE, value);
|
|
}
|
|
}
|