TeaWeb/shared/js/audio/Recorder.ts

162 lines
4.4 KiB
TypeScript

import {AbstractInput, LevelMeter} from "../voice/RecorderBase";
import {Registry} from "../events";
export interface AudioRecorderBacked {
createInput() : AbstractInput;
createLevelMeter(device: InputDevice) : Promise<LevelMeter>;
getDeviceList() : DeviceList;
}
export interface DeviceListEvents {
notify_list_updated: {
removedDeviceCount: number,
addedDeviceCount: number
},
notify_state_changed: {
oldState: DeviceListState;
newState: DeviceListState;
},
notify_permissions_changed: {
oldState: PermissionState,
newState: PermissionState
}
}
export type DeviceListState = "healthy" | "uninitialized" | "no-permissions" | "error";
export interface InputDevice {
deviceId: string;
driver: string;
name: string;
}
export namespace InputDevice {
export const NoDeviceId = "none";
export const DefaultDeviceId = "default";
}
export type PermissionState = "granted" | "denied" | "unknown";
export interface DeviceList {
getEvents() : Registry<DeviceListEvents>;
isRefreshAvailable() : boolean;
refresh() : Promise<void>;
/* implicitly update our own permission state */
requestPermissions() : Promise<PermissionState>;
getPermissionState() : PermissionState;
getStatus() : DeviceListState;
getDevices() : InputDevice[];
getDefaultDeviceId() : string;
awaitHealthy(): Promise<void>;
awaitInitialized() : Promise<void>;
}
export abstract class AbstractDeviceList implements DeviceList {
protected readonly events: Registry<DeviceListEvents>;
protected listState: DeviceListState;
protected permissionState: PermissionState;
protected constructor() {
this.events = new Registry<DeviceListEvents>();
this.permissionState = "unknown";
this.listState = "uninitialized";
}
getStatus(): DeviceListState {
return this.listState;
}
getPermissionState(): PermissionState {
return this.permissionState;
}
protected setState(state: DeviceListState) {
if(this.listState === state) {
return;
}
const oldState = this.listState;
this.listState = state;
this.events.fire("notify_state_changed", { oldState: oldState, newState: state });
}
protected setPermissionState(state: PermissionState) {
if(this.permissionState === state) {
return;
}
const oldState = this.permissionState;
this.permissionState = state;
this.events.fire("notify_permissions_changed", { oldState: oldState, newState: state });
}
awaitInitialized(): Promise<void> {
if(this.listState !== "uninitialized") {
return Promise.resolve();
}
return new Promise<void>(resolve => {
const callback = (event: DeviceListEvents["notify_state_changed"]) => {
if(event.newState === "uninitialized") {
return;
}
this.events.off("notify_state_changed", callback);
resolve();
};
this.events.on("notify_state_changed", callback);
});
}
awaitHealthy(): Promise<void> {
if(this.listState === "healthy") {
return Promise.resolve();
}
return new Promise<void>(resolve => {
const callback = (event: DeviceListEvents["notify_state_changed"]) => {
if(event.newState !== "healthy")
return;
this.events.off("notify_state_changed", callback);
resolve();
};
this.events.on("notify_state_changed", callback);
});
}
abstract getDefaultDeviceId(): string;
abstract getDevices(): InputDevice[];
abstract getEvents(): Registry<DeviceListEvents>;
abstract isRefreshAvailable(): boolean;
abstract refresh(): Promise<void>;
abstract requestPermissions(): Promise<PermissionState>;
}
let recorderBackend: AudioRecorderBacked;
export function getRecorderBackend() : AudioRecorderBacked {
if(typeof recorderBackend === "undefined") {
throw tr("the recorder backend hasn't been set yet");
}
return recorderBackend;
}
export function setRecorderBackend(instance: AudioRecorderBacked) {
if(typeof recorderBackend !== "undefined") {
throw tr("a recorder backend has already been initialized");
}
recorderBackend = instance;
}