2021-06-23 07:26:33 +00:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2022-12-12 11:24:14 +00:00
|
|
|
import EventEmitter from "events";
|
2021-10-22 22:23:32 +00:00
|
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
|
|
|
|
2021-06-23 07:26:33 +00:00
|
|
|
import SettingsStore from "./settings/SettingsStore";
|
|
|
|
import { SettingLevel } from "./settings/SettingLevel";
|
2021-08-29 07:39:34 +00:00
|
|
|
import { MatrixClientPeg } from "./MatrixClientPeg";
|
2022-12-12 11:24:14 +00:00
|
|
|
import { _t } from "./languageHandler";
|
2021-06-23 07:26:33 +00:00
|
|
|
|
2021-07-09 11:08:39 +00:00
|
|
|
// XXX: MediaDeviceKind is a union type, so we make our own enum
|
|
|
|
export enum MediaDeviceKindEnum {
|
|
|
|
AudioOutput = "audiooutput",
|
|
|
|
AudioInput = "audioinput",
|
|
|
|
VideoInput = "videoinput",
|
2021-06-23 07:26:33 +00:00
|
|
|
}
|
|
|
|
|
2021-07-09 11:08:39 +00:00
|
|
|
export type IMediaDevices = Record<MediaDeviceKindEnum, Array<MediaDeviceInfo>>;
|
|
|
|
|
2021-06-23 07:56:37 +00:00
|
|
|
export enum MediaDeviceHandlerEvent {
|
|
|
|
AudioOutputChanged = "audio_output_changed",
|
|
|
|
}
|
|
|
|
|
|
|
|
export default class MediaDeviceHandler extends EventEmitter {
|
2023-02-13 11:39:16 +00:00
|
|
|
private static internalInstance?: MediaDeviceHandler;
|
2021-06-23 07:56:37 +00:00
|
|
|
|
|
|
|
public static get instance(): MediaDeviceHandler {
|
|
|
|
if (!MediaDeviceHandler.internalInstance) {
|
|
|
|
MediaDeviceHandler.internalInstance = new MediaDeviceHandler();
|
|
|
|
}
|
|
|
|
return MediaDeviceHandler.internalInstance;
|
|
|
|
}
|
|
|
|
|
2021-06-23 08:27:51 +00:00
|
|
|
public static async hasAnyLabeledDevices(): Promise<boolean> {
|
2021-06-23 07:26:33 +00:00
|
|
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
2022-12-12 11:24:14 +00:00
|
|
|
return devices.some((d) => Boolean(d.label));
|
2021-06-23 07:26:33 +00:00
|
|
|
}
|
|
|
|
|
2022-09-30 16:28:53 +00:00
|
|
|
/**
|
|
|
|
* Gets the available audio input/output and video input devices
|
|
|
|
* from the browser: a thin wrapper around mediaDevices.enumerateDevices()
|
|
|
|
* that also returns results by type of devices. Note that this requires
|
|
|
|
* user media permissions and an active stream, otherwise you'll get blank
|
|
|
|
* device labels.
|
|
|
|
*
|
|
|
|
* Once the Permissions API
|
|
|
|
* (https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API)
|
|
|
|
* is ready for primetime, it might help make this simpler.
|
|
|
|
*
|
|
|
|
* @return Promise<IMediaDevices> The available media devices
|
|
|
|
*/
|
2023-02-24 15:28:40 +00:00
|
|
|
public static async getDevices(): Promise<IMediaDevices | undefined> {
|
2021-06-23 07:26:33 +00:00
|
|
|
try {
|
|
|
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
2023-02-13 11:39:16 +00:00
|
|
|
const output: Record<MediaDeviceKindEnum, MediaDeviceInfo[]> = {
|
2021-07-09 11:08:39 +00:00
|
|
|
[MediaDeviceKindEnum.AudioOutput]: [],
|
|
|
|
[MediaDeviceKindEnum.AudioInput]: [],
|
|
|
|
[MediaDeviceKindEnum.VideoInput]: [],
|
|
|
|
};
|
|
|
|
|
|
|
|
devices.forEach((device) => output[device.kind].push(device));
|
|
|
|
return output;
|
2021-06-23 07:26:33 +00:00
|
|
|
} catch (error) {
|
2022-12-12 11:24:14 +00:00
|
|
|
logger.warn("Unable to refresh WebRTC Devices: ", error);
|
2021-06-23 07:26:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-15 09:02:40 +00:00
|
|
|
public static getDefaultDevice = (devices: Array<Partial<MediaDeviceInfo>>): string => {
|
|
|
|
// Note we're looking for a device with deviceId 'default' but adding a device
|
|
|
|
// with deviceId == the empty string: this is because Chrome gives us a device
|
|
|
|
// with deviceId 'default', so we're looking for this, not the one we are adding.
|
2022-12-12 11:24:14 +00:00
|
|
|
if (!devices.some((i) => i.deviceId === "default")) {
|
2023-09-22 15:39:40 +00:00
|
|
|
devices.unshift({ deviceId: "", label: _t("voip|default_device") });
|
2022-12-12 11:24:14 +00:00
|
|
|
return "";
|
2022-11-15 09:02:40 +00:00
|
|
|
} else {
|
2022-12-12 11:24:14 +00:00
|
|
|
return "default";
|
2022-11-15 09:02:40 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-06-23 08:37:15 +00:00
|
|
|
/**
|
|
|
|
* Retrieves devices from the SettingsStore and tells the js-sdk to use them
|
|
|
|
*/
|
2022-05-04 14:41:56 +00:00
|
|
|
public static async loadDevices(): Promise<void> {
|
2021-06-23 07:26:33 +00:00
|
|
|
const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
|
|
|
|
const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
|
|
|
|
|
2023-06-21 16:29:44 +00:00
|
|
|
await MatrixClientPeg.safeGet().getMediaHandler().setAudioInput(audioDeviceId);
|
|
|
|
await MatrixClientPeg.safeGet().getMediaHandler().setVideoInput(videoDeviceId);
|
2022-11-09 20:14:55 +00:00
|
|
|
|
|
|
|
await MediaDeviceHandler.updateAudioSettings();
|
|
|
|
}
|
|
|
|
|
|
|
|
private static async updateAudioSettings(): Promise<void> {
|
2023-06-21 16:29:44 +00:00
|
|
|
await MatrixClientPeg.safeGet().getMediaHandler().setAudioSettings({
|
2022-11-09 20:14:55 +00:00
|
|
|
autoGainControl: MediaDeviceHandler.getAudioAutoGainControl(),
|
|
|
|
echoCancellation: MediaDeviceHandler.getAudioEchoCancellation(),
|
|
|
|
noiseSuppression: MediaDeviceHandler.getAudioNoiseSuppression(),
|
|
|
|
});
|
2021-06-23 07:26:33 +00:00
|
|
|
}
|
|
|
|
|
2021-06-23 08:27:51 +00:00
|
|
|
public setAudioOutput(deviceId: string): void {
|
2021-06-23 07:26:33 +00:00
|
|
|
SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
|
2021-06-23 07:56:37 +00:00
|
|
|
this.emit(MediaDeviceHandlerEvent.AudioOutputChanged, deviceId);
|
2021-06-23 07:26:33 +00:00
|
|
|
}
|
|
|
|
|
2021-06-23 08:37:15 +00:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2022-05-04 14:41:56 +00:00
|
|
|
public async setAudioInput(deviceId: string): Promise<void> {
|
2021-06-23 07:26:33 +00:00
|
|
|
SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId);
|
2023-06-21 16:29:44 +00:00
|
|
|
return MatrixClientPeg.safeGet().getMediaHandler().setAudioInput(deviceId);
|
2021-06-23 07:26:33 +00:00
|
|
|
}
|
|
|
|
|
2021-06-23 08:37:15 +00:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2022-05-04 14:41:56 +00:00
|
|
|
public async setVideoInput(deviceId: string): Promise<void> {
|
2021-06-23 07:26:33 +00:00
|
|
|
SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId);
|
2023-06-21 16:29:44 +00:00
|
|
|
return MatrixClientPeg.safeGet().getMediaHandler().setVideoInput(deviceId);
|
2021-06-23 07:26:33 +00:00
|
|
|
}
|
|
|
|
|
2022-05-04 14:41:56 +00:00
|
|
|
public async setDevice(deviceId: string, kind: MediaDeviceKindEnum): Promise<void> {
|
2021-07-09 12:13:31 +00:00
|
|
|
switch (kind) {
|
2022-12-12 11:24:14 +00:00
|
|
|
case MediaDeviceKindEnum.AudioOutput:
|
|
|
|
this.setAudioOutput(deviceId);
|
|
|
|
break;
|
|
|
|
case MediaDeviceKindEnum.AudioInput:
|
|
|
|
await this.setAudioInput(deviceId);
|
|
|
|
break;
|
|
|
|
case MediaDeviceKindEnum.VideoInput:
|
|
|
|
await this.setVideoInput(deviceId);
|
|
|
|
break;
|
2021-07-09 12:13:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-09 20:14:55 +00:00
|
|
|
public static async setAudioAutoGainControl(value: boolean): Promise<void> {
|
|
|
|
await SettingsStore.setValue("webrtc_audio_autoGainControl", null, SettingLevel.DEVICE, value);
|
|
|
|
await MediaDeviceHandler.updateAudioSettings();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static async setAudioEchoCancellation(value: boolean): Promise<void> {
|
|
|
|
await SettingsStore.setValue("webrtc_audio_echoCancellation", null, SettingLevel.DEVICE, value);
|
|
|
|
await MediaDeviceHandler.updateAudioSettings();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static async setAudioNoiseSuppression(value: boolean): Promise<void> {
|
|
|
|
await SettingsStore.setValue("webrtc_audio_noiseSuppression", null, SettingLevel.DEVICE, value);
|
|
|
|
await MediaDeviceHandler.updateAudioSettings();
|
|
|
|
}
|
|
|
|
|
2021-06-23 08:27:51 +00:00
|
|
|
public static getAudioOutput(): string {
|
2021-06-23 07:26:33 +00:00
|
|
|
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audiooutput");
|
|
|
|
}
|
|
|
|
|
2021-06-23 08:27:51 +00:00
|
|
|
public static getAudioInput(): string {
|
2021-06-23 07:26:33 +00:00
|
|
|
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audioinput");
|
|
|
|
}
|
|
|
|
|
2021-06-23 08:27:51 +00:00
|
|
|
public static getVideoInput(): string {
|
2021-06-23 07:26:33 +00:00
|
|
|
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_videoinput");
|
|
|
|
}
|
2022-05-04 14:41:56 +00:00
|
|
|
|
2022-11-09 20:14:55 +00:00
|
|
|
public static getAudioAutoGainControl(): boolean {
|
|
|
|
return SettingsStore.getValue("webrtc_audio_autoGainControl");
|
|
|
|
}
|
|
|
|
|
|
|
|
public static getAudioEchoCancellation(): boolean {
|
|
|
|
return SettingsStore.getValue("webrtc_audio_echoCancellation");
|
|
|
|
}
|
|
|
|
|
|
|
|
public static getAudioNoiseSuppression(): boolean {
|
|
|
|
return SettingsStore.getValue("webrtc_audio_noiseSuppression");
|
|
|
|
}
|
|
|
|
|
2022-05-04 14:41:56 +00:00
|
|
|
/**
|
|
|
|
* 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) {
|
2022-12-12 11:24:14 +00:00
|
|
|
case MediaDeviceKindEnum.AudioOutput:
|
|
|
|
return this.getAudioOutput();
|
|
|
|
case MediaDeviceKindEnum.AudioInput:
|
|
|
|
return this.getAudioInput();
|
|
|
|
case MediaDeviceKindEnum.VideoInput:
|
|
|
|
return this.getVideoInput();
|
2022-05-04 14:41:56 +00:00
|
|
|
}
|
|
|
|
}
|
2022-08-30 19:13:39 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2021-06-23 07:26:33 +00:00
|
|
|
}
|