176 lines
5.2 KiB
TypeScript
176 lines
5.2 KiB
TypeScript
import {
|
|
AbstractDeviceList,
|
|
DeviceListEvents,
|
|
DeviceListState,
|
|
InputDevice,
|
|
PermissionState
|
|
} from "tc-shared/audio/Recorder";
|
|
import {LogCategory, logDebug, logError} from "tc-shared/log";
|
|
import {Registry} from "tc-shared/events";
|
|
import {WebIDevice} from "./Recorder";
|
|
import * as loader from "tc-loader";
|
|
import {queryMediaPermissions} from "tc-shared/media/Stream";
|
|
import { tr } from "tc-shared/i18n/localize";
|
|
|
|
async function requestMicrophonePermissions() : Promise<PermissionState> {
|
|
const begin = Date.now();
|
|
try {
|
|
await navigator.mediaDevices.getUserMedia({ audio: { deviceId: "default" }, video: false });
|
|
return "granted";
|
|
} catch (error) {
|
|
const end = Date.now();
|
|
const isSystem = (end - begin) < 250;
|
|
logDebug(LogCategory.AUDIO, tr("Microphone device request took %d milliseconds. System answered: %s"), end - begin, isSystem);
|
|
return "denied";
|
|
}
|
|
}
|
|
|
|
export let inputDeviceList: WebInputDeviceList;
|
|
class WebInputDeviceList extends AbstractDeviceList {
|
|
private devices: WebIDevice[];
|
|
|
|
private deviceListQueryPromise: Promise<void>;
|
|
|
|
constructor() {
|
|
super();
|
|
|
|
this.devices = [];
|
|
}
|
|
|
|
async initialize() {
|
|
const result = await queryMediaPermissions("audio");
|
|
if(typeof result === "boolean") {
|
|
this.setPermissionState(result ? "granted" : "denied");
|
|
}
|
|
}
|
|
|
|
getDefaultDeviceId(): string {
|
|
return "default";
|
|
}
|
|
|
|
getDevices(): InputDevice[] {
|
|
return this.devices;
|
|
}
|
|
|
|
getEvents(): Registry<DeviceListEvents> {
|
|
return this.events;
|
|
}
|
|
|
|
getStatus(): DeviceListState {
|
|
return this.listState;
|
|
}
|
|
|
|
isRefreshAvailable(): boolean {
|
|
return true;
|
|
}
|
|
|
|
refresh(askPermissions?: boolean): Promise<void> {
|
|
return this.queryDevices(askPermissions === true);
|
|
}
|
|
|
|
async requestPermissions(): Promise<PermissionState> {
|
|
if(this.permissionState !== "unknown")
|
|
return this.permissionState;
|
|
|
|
let result = await requestMicrophonePermissions();
|
|
if(result === "granted" && this.listState === "no-permissions") {
|
|
/* if called within doQueryDevices, queryDevices will just return the promise */
|
|
this.queryDevices(false).then(() => {});
|
|
}
|
|
this.setPermissionState(result);
|
|
return result;
|
|
}
|
|
|
|
private queryDevices(askPermissions: boolean) : Promise<void> {
|
|
if(this.deviceListQueryPromise) {
|
|
return this.deviceListQueryPromise;
|
|
}
|
|
|
|
this.deviceListQueryPromise = this.doQueryDevices(askPermissions).catch(error => {
|
|
logError(LogCategory.AUDIO, tr("Failed to query microphone devices (%o)"), error);
|
|
|
|
if(this.listState !== "healthy") {
|
|
this.setState("error");
|
|
}
|
|
}).then(() => {
|
|
this.deviceListQueryPromise = undefined;
|
|
});
|
|
|
|
return this.deviceListQueryPromise;
|
|
}
|
|
|
|
private async doQueryDevices(askPermissions: boolean) {
|
|
let devices = await navigator.mediaDevices.enumerateDevices();
|
|
let hasPermissions = devices.findIndex(e => e.label !== "") !== -1;
|
|
|
|
if(!hasPermissions && askPermissions) {
|
|
this.setState("no-permissions");
|
|
|
|
/* request permissions */
|
|
hasPermissions = await this.requestPermissions() === "granted";
|
|
if(hasPermissions) {
|
|
devices = await navigator.mediaDevices.enumerateDevices();
|
|
}
|
|
}
|
|
if(hasPermissions) {
|
|
this.setPermissionState("granted");
|
|
}
|
|
|
|
if(window.detectedBrowser?.name === "firefox") {
|
|
devices = [{
|
|
label: tr("Default Firefox device"),
|
|
groupId: "default",
|
|
deviceId: "default",
|
|
kind: "audioinput",
|
|
|
|
toJSON: undefined
|
|
}];
|
|
}
|
|
|
|
const inputDevices = devices.filter(e => e.kind === "audioinput");
|
|
|
|
const oldDeviceList = this.devices;
|
|
this.devices = [];
|
|
|
|
let devicesAdded = 0;
|
|
for(const device of inputDevices) {
|
|
const oldIndex = oldDeviceList.findIndex(e => e.deviceId === device.deviceId);
|
|
if(oldIndex === -1) {
|
|
devicesAdded++;
|
|
} else {
|
|
oldDeviceList.splice(oldIndex, 1);
|
|
}
|
|
|
|
this.devices.push({
|
|
deviceId: device.deviceId,
|
|
driver: "WebAudio",
|
|
groupId: device.groupId,
|
|
name: device.label
|
|
});
|
|
}
|
|
|
|
this.events.fire("notify_list_updated", { addedDeviceCount: devicesAdded, removedDeviceCount: oldDeviceList.length });
|
|
if(hasPermissions) {
|
|
this.setState("healthy");
|
|
} else {
|
|
this.setState("no-permissions");
|
|
}
|
|
}
|
|
}
|
|
|
|
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|
function: async () => {
|
|
inputDeviceList = new WebInputDeviceList();
|
|
await inputDeviceList.initialize();
|
|
},
|
|
priority: 80,
|
|
name: "initialize media devices"
|
|
});
|
|
|
|
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|
function: async () => {
|
|
inputDeviceList.refresh(false).then(() => {});
|
|
},
|
|
priority: 10,
|
|
name: "query media devices"
|
|
}); |