TeaSpeak-Client/modules/renderer/audio/AudioRecorder.ts

309 lines
9.1 KiB
TypeScript
Raw Normal View History

import {
AbstractInput,
2020-09-24 16:06:52 -04:00
FilterMode,
InputConsumer,
2020-08-21 07:37:10 -04:00
InputConsumerType,
InputEvents,
InputState,
2020-11-29 15:06:57 -05:00
LevelMeter, MediaStreamRequestResult
} from "tc-shared/voice/RecorderBase";
import {audio} from "tc-native/connection";
import {tr} from "tc-shared/i18n/localize";
2020-08-21 07:37:10 -04:00
import {Registry} from "tc-shared/events";
import {Filter, FilterType, FilterTypeClass} from "tc-shared/voice/Filter";
import {NativeFilter, NStateFilter, NThresholdFilter, NVoiceLevelFilter} from "./AudioFilter";
import {IDevice} from "tc-shared/audio/recorder";
import {LogCategory, logWarn} from "tc-shared/log";
2020-09-24 16:06:52 -04:00
import NativeFilterMode = audio.record.FilterMode;
2020-10-05 09:10:49 -04:00
import {Settings, settings} from "tc-shared/settings";
2020-08-21 07:37:10 -04:00
export class NativeInput implements AbstractInput {
2020-10-01 04:56:22 -04:00
static readonly instances = [] as NativeInput[];
2020-08-21 07:37:10 -04:00
readonly events: Registry<InputEvents>;
2019-10-25 19:51:40 -04:00
2020-10-01 04:56:22 -04:00
readonly nativeHandle: audio.record.AudioRecorder;
readonly nativeConsumer: audio.record.AudioConsumer;
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
private state: InputState;
private deviceId: string | undefined;
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
private registeredFilters: (Filter & NativeFilter)[] = [];
private filtered = false;
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
constructor() {
this.events = new Registry<InputEvents>();
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
this.nativeHandle = audio.record.create_recorder();
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
this.nativeConsumer = this.nativeHandle.create_consumer();
2020-10-05 09:10:49 -04:00
this.nativeConsumer.toggle_rnnoise(settings.static_global(Settings.KEY_RNNOISE_FILTER));
2020-10-01 04:56:22 -04:00
2020-08-21 07:37:10 -04:00
this.nativeConsumer.callback_ended = () => {
this.filtered = true;
this.events.fire("notify_voice_end");
};
this.nativeConsumer.callback_started = () => {
this.filtered = false;
this.events.fire("notify_voice_start");
};
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
this.state = InputState.PAUSED;
2020-10-01 04:56:22 -04:00
NativeInput.instances.push(this);
}
destroy() {
const index = NativeInput.instances.indexOf(this);
if(index !== -1) {
NativeInput.instances.splice(index, 1);
}
}
2019-10-25 19:51:40 -04:00
2020-11-29 15:06:57 -05:00
async start(): Promise<MediaStreamRequestResult | true> {
2020-08-21 07:37:10 -04:00
if(this.state === InputState.RECORDING) {
logWarn(LogCategory.VOICE, tr("Tried to start an input recorder twice."));
2020-11-29 15:06:57 -05:00
return MediaStreamRequestResult.EBUSY;
}
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
this.state = InputState.INITIALIZING;
try {
const state = await new Promise<audio.record.DeviceSetResult>(resolve => this.nativeHandle.set_device(this.deviceId, resolve));
if(state !== "success") {
if(state === "invalid-device") {
2020-11-29 15:06:57 -05:00
return MediaStreamRequestResult.EDEVICEUNKNOWN;
2020-08-21 07:37:10 -04:00
} else if(state === undefined) {
throw tr("invalid set device result state");
}
2020-11-29 15:06:57 -05:00
/* FIXME! */
2020-08-21 07:37:10 -04:00
throw state;
2019-10-25 19:51:40 -04:00
}
2020-08-21 07:37:10 -04:00
await new Promise((resolve, reject) => this.nativeHandle.start(result => {
if(result === true) {
resolve();
} else {
reject(typeof result === "string" ? result : tr("failed to start input"));
}
}));
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
this.state = InputState.RECORDING;
2020-11-29 15:06:57 -05:00
return true;
2020-08-21 07:37:10 -04:00
} finally {
if(this.state === InputState.INITIALIZING) {
this.state = InputState.PAUSED;
2019-10-25 19:51:40 -04:00
}
}
}
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
async stop(): Promise<void> {
if(this.state === InputState.PAUSED)
return;
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
this.nativeHandle.stop();
this.state = InputState.PAUSED;
}
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
async setDeviceId(device: string | undefined): Promise<void> {
2020-09-24 16:06:52 -04:00
if(this.deviceId === device) {
return;
2020-09-24 16:06:52 -04:00
}
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
this.deviceId = device;
await this.stop();
}
2020-08-21 07:37:10 -04:00
currentDeviceId(): string | undefined {
return this.deviceId;
}
2020-08-21 07:37:10 -04:00
isFiltered(): boolean {
return this.filtered;
}
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
removeFilter(filter: Filter) {
const index = this.registeredFilters.indexOf(filter as any);
if(index === -1) return;
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
const [ f ] = this.registeredFilters.splice(index, 1);
f.finalize();
}
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
createFilter<T extends FilterType>(type: T, priority: number): FilterTypeClass<T> {
let filter;
switch (type) {
2020-08-21 07:37:10 -04:00
case FilterType.STATE:
filter = new NStateFilter(this, priority);
break;
2020-08-21 07:37:10 -04:00
case FilterType.THRESHOLD:
filter = new NThresholdFilter(this, priority);
break;
2020-08-21 07:37:10 -04:00
case FilterType.VOICE_LEVEL:
filter = new NVoiceLevelFilter(this, priority);
break;
2019-10-25 19:51:40 -04:00
}
2020-08-21 07:37:10 -04:00
this.registeredFilters.push(filter);
return filter;
}
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
supportsFilter(type: FilterType): boolean {
switch (type) {
2020-08-21 07:37:10 -04:00
case FilterType.VOICE_LEVEL:
case FilterType.THRESHOLD:
case FilterType.STATE:
return true;
2020-08-21 07:37:10 -04:00
default:
return false;
2019-10-25 19:51:40 -04:00
}
}
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
currentState(): InputState {
return this.state;
}
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
currentConsumer(): InputConsumer | undefined {
return {
type: InputConsumerType.NATIVE
};
}
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
getNativeConsumer() : audio.record.AudioConsumer {
return this.nativeConsumer;
2019-10-25 19:51:40 -04:00
}
2020-08-21 07:37:10 -04:00
async setConsumer(consumer: InputConsumer): Promise<void> {
2020-09-24 16:06:52 -04:00
if(typeof(consumer) !== "undefined") {
2020-08-21 07:37:10 -04:00
throw "we only support native consumers!"; // TODO: May create a general wrapper?
2020-09-24 16:06:52 -04:00
}
2020-08-21 07:37:10 -04:00
return;
2019-10-25 19:51:40 -04:00
}
2020-08-21 07:37:10 -04:00
setVolume(volume: number) {
this.nativeHandle.set_volume(volume);
}
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
getVolume(): number {
return this.nativeHandle.get_volume();
}
2020-09-24 16:06:52 -04:00
getFilterMode(): FilterMode {
const mode = this.nativeConsumer.get_filter_mode();
switch (mode) {
case NativeFilterMode.Block:
return FilterMode.Block;
case NativeFilterMode.Bypass:
return FilterMode.Bypass;
case NativeFilterMode.Filter:
default:
return FilterMode.Filter;
}
}
setFilterMode(mode: FilterMode) {
let nativeMode: NativeFilterMode;
switch (mode) {
case FilterMode.Filter:
nativeMode = NativeFilterMode.Filter;
break;
case FilterMode.Bypass:
nativeMode = NativeFilterMode.Bypass;
break;
case FilterMode.Block:
nativeMode = NativeFilterMode.Block;
break;
}
this.nativeConsumer.set_filter_mode(nativeMode);
}
}
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
export class NativeLevelMeter implements LevelMeter {
2020-10-01 04:56:22 -04:00
static readonly instances: NativeLevelMeter[] = [];
readonly targetDevice: IDevice;
2019-10-25 19:51:40 -04:00
2020-10-01 04:56:22 -04:00
public nativeRecorder: audio.record.AudioRecorder;
public nativeConsumer: audio.record.AudioConsumer;
private callback: (num: number) => any;
private nativeFilter: audio.record.ThresholdConsumeFilter;
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
constructor(device: IDevice) {
2020-10-01 04:56:22 -04:00
this.targetDevice = device;
}
2019-10-25 19:51:40 -04:00
async initialize() {
try {
2020-10-01 04:56:22 -04:00
this.nativeRecorder = audio.record.create_recorder();
this.nativeConsumer = this.nativeRecorder.create_consumer();
2020-10-05 09:10:49 -04:00
this.nativeConsumer.toggle_rnnoise(settings.static_global(Settings.KEY_RNNOISE_FILTER));
2020-10-01 04:56:22 -04:00
this.nativeFilter = this.nativeConsumer.create_filter_threshold(.5);
this.nativeFilter.set_attack_smooth(.75);
this.nativeFilter.set_release_smooth(.75);
2020-10-01 04:56:22 -04:00
await new Promise(resolve => this.nativeRecorder.set_device(this.targetDevice.deviceId, resolve));
await new Promise((resolve, reject) => {
2020-10-01 04:56:22 -04:00
this.nativeRecorder.start(flag => {
if (typeof flag === "boolean" && flag)
resolve();
else
reject(typeof flag === "string" ? flag : "failed to start");
2019-10-25 19:51:40 -04:00
});
});
} catch (error) {
if (typeof (error) === "string")
throw error;
2020-10-01 04:56:22 -04:00
console.warn(tr("Failed to initialize levelmeter for device %o: %o"), this.targetDevice, error);
throw "initialize failed (lookup console)";
2019-10-25 19:51:40 -04:00
}
/* references this variable, needs a destory() call, else memory leak */
2020-10-01 04:56:22 -04:00
this.nativeFilter.set_analyze_filter(value => {
if(this.callback) this.callback(value);
});
2020-10-01 04:56:22 -04:00
NativeLevelMeter.instances.push(this);
}
2019-10-25 19:51:40 -04:00
2020-08-21 07:37:10 -04:00
destroy() {
2020-10-01 04:56:22 -04:00
const index = NativeLevelMeter.instances.indexOf(this);
if(index !== -1) {
NativeLevelMeter.instances.splice(index, 1);
}
if (this.nativeFilter) {
this.nativeFilter.set_analyze_filter(undefined);
this.nativeConsumer.unregister_filter(this.nativeFilter);
2019-10-25 19:51:40 -04:00
}
2020-08-21 07:37:10 -04:00
2020-10-01 04:56:22 -04:00
if (this.nativeConsumer) {
this.nativeRecorder.delete_consumer(this.nativeConsumer);
2020-08-21 07:37:10 -04:00
}
2020-10-01 04:56:22 -04:00
if(this.nativeRecorder) {
this.nativeRecorder.stop();
2020-08-21 07:37:10 -04:00
}
2020-10-01 04:56:22 -04:00
this.nativeRecorder = undefined;
this.nativeConsumer = undefined;
this.nativeFilter = undefined;
}
2019-10-25 19:51:40 -04:00
2020-09-24 16:06:52 -04:00
getDevice(): IDevice {
2020-10-01 04:56:22 -04:00
return this.targetDevice;
2019-10-25 19:51:40 -04:00
}
2020-09-24 16:06:52 -04:00
setObserver(callback: (value: number) => any) {
2020-10-01 04:56:22 -04:00
this.callback = callback;
}
}