Fixed microphone selection / autostart

This commit is contained in:
WolverinDEV 2021-01-08 22:04:27 +01:00
parent 90b5f3eacf
commit a0c2d5fcc2
6 changed files with 99 additions and 42 deletions

View File

@ -40,9 +40,9 @@ export class NThresholdFilter extends NativeFilter implements ThresholdFilter {
readonly type: FilterType.THRESHOLD; readonly type: FilterType.THRESHOLD;
private filter: audio.record.ThresholdConsumeFilter; private filter: audio.record.ThresholdConsumeFilter;
private _margin_frames: number = 25; /* 120ms */ private marginFrames: number = 25; /* 120ms */
private _threshold: number = 50; private threshold: number = 50;
private _callback_level: any; private callbackLevel: () => void;
private _attack_smooth = 0; private _attack_smooth = 0;
private _release_smooth = 0; private _release_smooth = 0;
@ -54,14 +54,15 @@ export class NThresholdFilter extends NativeFilter implements ThresholdFilter {
Object.defineProperty(this, 'callback_level', { Object.defineProperty(this, 'callback_level', {
get(): any { get(): any {
return this._callback_level; return this.callbackLevel;
}, set(v: any): void { }, set(v: any): void {
if(v === this._callback_level) if(v === this.callbackLevel)
return; return;
this._callback_level = v; this.callbackLevel = v;
if(this.filter) if(this.filter) {
this.filter.set_analyze_filter(v); this.filter.set_analyze_filter(v);
}
}, },
enumerable: true, enumerable: true,
configurable: false, configurable: false,
@ -69,15 +70,15 @@ export class NThresholdFilter extends NativeFilter implements ThresholdFilter {
} }
getMarginFrames(): number { getMarginFrames(): number {
return this.filter ? this.filter.get_margin_time() * NThresholdFilter.frames_per_second : this._margin_frames; return this.filter ? this.filter.get_margin_time() * NThresholdFilter.frames_per_second : this.marginFrames;
} }
getThreshold(): number { getThreshold(): number {
return this.filter ? this.filter.get_threshold() : this._threshold; return this.filter ? this.filter.get_threshold() : this.threshold;
} }
setMarginFrames(value: number) { setMarginFrames(value: number) {
this._margin_frames = value; this.marginFrames = value;
if(this.filter) if(this.filter)
this.filter.set_margin_time(value / 960 / 1000); this.filter.set_margin_time(value / 960 / 1000);
} }
@ -105,7 +106,7 @@ export class NThresholdFilter extends NativeFilter implements ThresholdFilter {
setThreshold(value: number): Promise<void> { setThreshold(value: number): Promise<void> {
if(typeof(value) === "string") if(typeof(value) === "string")
value = parseInt(value); /* yes... this happens */ value = parseInt(value); /* yes... this happens */
this._threshold = value; this.threshold = value;
if(this.filter) if(this.filter)
this.filter.set_threshold(value); this.filter.set_threshold(value);
return Promise.resolve(); return Promise.resolve();
@ -113,35 +114,49 @@ export class NThresholdFilter extends NativeFilter implements ThresholdFilter {
finalize() { finalize() {
if(this.filter) { if(this.filter) {
if(this.handle.getNativeConsumer()) if(this.handle.getNativeConsumer()) {
this.handle.getNativeConsumer().unregister_filter(this.filter); this.handle.getNativeConsumer().unregister_filter(this.filter);
}
this.filter = undefined; this.filter = undefined;
} }
} }
initialize() { initialize() {
const consumer = this.handle.getNativeConsumer(); const consumer = this.handle.getNativeConsumer();
if(!consumer) if(!consumer) {
return; return;
}
this.finalize(); this.finalize();
this.filter = consumer.create_filter_threshold(this._threshold); this.filter = consumer.create_filter_threshold(this.threshold);
if(this._callback_level) this.filter.set_margin_time(this.marginFrames / NThresholdFilter.frames_per_second);
this.filter.set_analyze_filter(this._callback_level);
this.filter.set_margin_time(this._margin_frames / NThresholdFilter.frames_per_second);
this.filter.set_attack_smooth(this._attack_smooth); this.filter.set_attack_smooth(this._attack_smooth);
this.filter.set_release_smooth(this._release_smooth); this.filter.set_release_smooth(this._release_smooth);
this.updateAnalyzeFilterCallback();
} }
registerLevelCallback(callback: (value: number) => void) { registerLevelCallback(callback: (value: number) => void) {
this.levelCallbacks.push(callback); this.levelCallbacks.push(callback);
this.updateAnalyzeFilterCallback();
} }
removeLevelCallback(callback: (value: number) => void) { removeLevelCallback(callback: (value: number) => void) {
const index = this.levelCallbacks.indexOf(callback); const index = this.levelCallbacks.indexOf(callback);
if(index === -1) return; if(index === -1) {
return;
}
this.levelCallbacks.splice(index, 1); this.levelCallbacks.splice(index, 1);
this.updateAnalyzeFilterCallback();
}
private updateAnalyzeFilterCallback() {
if(this.levelCallbacks.length > 0) {
this.filter.set_analyze_filter(value => this.levelCallbacks.forEach(callback => callback(value)));
} else {
this.filter.set_analyze_filter(undefined);
}
} }
} }

View File

@ -4,8 +4,9 @@ import {
InputConsumer, InputConsumer,
InputConsumerType, InputConsumerType,
InputEvents, InputEvents,
InputStartError,
InputState, InputState,
LevelMeter, MediaStreamRequestResult LevelMeter,
} from "tc-shared/voice/RecorderBase"; } from "tc-shared/voice/RecorderBase";
import {audio} from "tc-native/connection"; import {audio} from "tc-native/connection";
import {tr} from "tc-shared/i18n/localize"; import {tr} from "tc-shared/i18n/localize";
@ -13,9 +14,9 @@ import {Registry} from "tc-shared/events";
import {Filter, FilterType, FilterTypeClass} from "tc-shared/voice/Filter"; import {Filter, FilterType, FilterTypeClass} from "tc-shared/voice/Filter";
import {NativeFilter, NStateFilter, NThresholdFilter, NVoiceLevelFilter} from "./AudioFilter"; import {NativeFilter, NStateFilter, NThresholdFilter, NVoiceLevelFilter} from "./AudioFilter";
import {IDevice} from "tc-shared/audio/recorder"; import {IDevice} from "tc-shared/audio/recorder";
import {LogCategory, logWarn} from "tc-shared/log"; import {LogCategory, logTrace, logWarn} from "tc-shared/log";
import NativeFilterMode = audio.record.FilterMode;
import {Settings, settings} from "tc-shared/settings"; import {Settings, settings} from "tc-shared/settings";
import NativeFilterMode = audio.record.FilterMode;
export class NativeInput implements AbstractInput { export class NativeInput implements AbstractInput {
static readonly instances = [] as NativeInput[]; static readonly instances = [] as NativeInput[];
@ -59,19 +60,30 @@ export class NativeInput implements AbstractInput {
} }
} }
async start(): Promise<MediaStreamRequestResult | true> { private setState(newState: InputState) {
if(this.state === InputState.RECORDING) { if(this.state === newState) {
logWarn(LogCategory.VOICE, tr("Tried to start an input recorder twice.")); return;
return MediaStreamRequestResult.EBUSY;
} }
this.state = InputState.INITIALIZING; const oldState = this.state;
this.state = newState;
this.events.fire("notify_state_changed", { oldState, newState });
}
async start(): Promise<InputStartError | true> {
if(this.state !== InputState.PAUSED) {
logWarn(LogCategory.VOICE, tr("Input isn't paused"));
return InputStartError.EBUSY;
}
this.setState(InputState.INITIALIZING);
logTrace(LogCategory.AUDIO, tr("Starting input for device %o", this.deviceId));
try { try {
const state = await new Promise<audio.record.DeviceSetResult>(resolve => this.nativeHandle.set_device(this.deviceId, resolve)); const state = await new Promise<audio.record.DeviceSetResult>(resolve => this.nativeHandle.set_device(this.deviceId, resolve));
if(state !== "success") { if(state !== "success") {
if(state === "invalid-device") { if(state === "invalid-device") {
return MediaStreamRequestResult.EDEVICEUNKNOWN; return InputStartError.EDEVICEUNKNOWN;
} else if(state === undefined) { } else if(state === undefined) {
throw tr("invalid set device result state"); throw tr("invalid set device result state");
} }
@ -88,21 +100,28 @@ export class NativeInput implements AbstractInput {
} }
})); }));
this.state = InputState.RECORDING; this.setState(InputState.RECORDING);
return true; return true;
} finally { } finally {
/* @ts-ignore Typescript isn't smart about awaits in try catch blocks */
if(this.state === InputState.INITIALIZING) { if(this.state === InputState.INITIALIZING) {
this.state = InputState.PAUSED; this.setState(InputState.PAUSED);
} }
} }
} }
async stop(): Promise<void> { async stop(): Promise<void> {
if(this.state === InputState.PAUSED) if(this.state === InputState.PAUSED) {
return; return;
}
this.nativeHandle.stop(); this.nativeHandle.stop();
this.state = InputState.PAUSED; this.setState(InputState.PAUSED);
if(this.filtered) {
this.filtered = false;
this.events.fire("notify_voice_end");
}
} }
async setDeviceId(device: string | undefined): Promise<void> { async setDeviceId(device: string | undefined): Promise<void> {
@ -110,8 +129,15 @@ export class NativeInput implements AbstractInput {
return; return;
} }
this.deviceId = device; try {
await this.stop(); await this.stop();
} catch (error) {
logWarn(LogCategory.GENERAL, tr("Failed to stop microphone recording after device change: %o"), error);
}
const oldDeviceId = this.deviceId;
this.deviceId = device;
this.events.fire("notify_device_changed", { oldDeviceId, newDeviceId: device });
} }
currentDeviceId(): string | undefined { currentDeviceId(): string | undefined {
@ -124,10 +150,12 @@ export class NativeInput implements AbstractInput {
removeFilter(filter: Filter) { removeFilter(filter: Filter) {
const index = this.registeredFilters.indexOf(filter as any); const index = this.registeredFilters.indexOf(filter as any);
if(index === -1) return; if(index === -1) {
return;
}
const [ f ] = this.registeredFilters.splice(index, 1); const [ registeredFilter ] = this.registeredFilters.splice(index, 1);
f.finalize(); registeredFilter.finalize();
} }
createFilter<T extends FilterType>(type: T, priority: number): FilterTypeClass<T> { createFilter<T extends FilterType>(type: T, priority: number): FilterTypeClass<T> {
@ -223,7 +251,13 @@ export class NativeInput implements AbstractInput {
break; break;
} }
if(this.nativeConsumer.get_filter_mode() === nativeMode) {
return;
}
const oldMode = this.getFilterMode();
this.nativeConsumer.set_filter_mode(nativeMode); this.nativeConsumer.set_filter_mode(nativeMode);
this.events.fire("notify_filter_mode_changed", { oldMode, newMode: mode });
} }
} }
@ -262,13 +296,15 @@ export class NativeLevelMeter implements LevelMeter {
}); });
}); });
} catch (error) { } catch (error) {
if (typeof (error) === "string") if (typeof (error) === "string") {
throw error; throw error;
console.warn(tr("Failed to initialize levelmeter for device %o: %o"), this.targetDevice, error); }
logWarn(LogCategory.AUDIO, tr("Failed to initialize level meter for device %o: %o"), this.targetDevice, error);
throw "initialize failed (lookup console)"; throw "initialize failed (lookup console)";
} }
/* references this variable, needs a destory() call, else memory leak */ /* references this variable, needs a destroy() call, else memory leak */
this.nativeFilter.set_analyze_filter(value => { this.nativeFilter.set_analyze_filter(value => {
if(this.callback) this.callback(value); if(this.callback) this.callback(value);
}); });

View File

@ -4,6 +4,7 @@ import * as loader from "tc-loader";
import * as native from "tc-native/connection"; import * as native from "tc-native/connection";
import {audio} from "tc-native/connection"; import {audio} from "tc-native/connection";
import {LogCategory, logTrace} from "tc-shared/log";
interface NativeIDevice extends IDevice { interface NativeIDevice extends IDevice {
isDefault: boolean isDefault: boolean
@ -39,7 +40,9 @@ class InputDeviceList extends AbstractDeviceList {
return this.cachedDevices; return this.cachedDevices;
} }
this.cachedDevices = audio.available_devices() const nativeDeviceList = audio.available_devices();
logTrace(LogCategory.AUDIO, tr("Native device list: %o"), nativeDeviceList);
this.cachedDevices = nativeDeviceList
.filter(e => e.input_supported || e.input_default) .filter(e => e.input_supported || e.input_default)
.filter(e => e.driver !== "Windows WDM-KS") /* If we're using WDM-KS and opening the microphone view, for some reason the channels get blocked an never release.... */ .filter(e => e.driver !== "Windows WDM-KS") /* If we're using WDM-KS and opening the microphone view, for some reason the channels get blocked an never release.... */
.map(device => { .map(device => {

View File

@ -93,6 +93,7 @@ export class NativeVoiceConnectionWrapper extends AbstractVoiceConnection {
} }
await recorder?.unmount(); await recorder?.unmount();
const oldRecorder = recorder;
this.currentRecorder = recorder; this.currentRecorder = recorder;
try { try {
@ -117,7 +118,10 @@ export class NativeVoiceConnectionWrapper extends AbstractVoiceConnection {
this.currentRecorder = undefined; this.currentRecorder = undefined;
throw error; throw error;
} }
this.events.fire("notify_recorder_changed", {}); this.events.fire("notify_recorder_changed", {
oldRecorder,
newRecorder: recorder
});
} }
voiceRecorder(): RecorderProfile { voiceRecorder(): RecorderProfile {

View File

@ -228,7 +228,6 @@ declare module "tc-native/connection" {
readonly channelCount: number; readonly channelCount: number;
readonly frameSize: number; readonly frameSize: number;
/* TODO add some kind of order to may improve CPU performance (Some filters are more intense then others) */
get_filters() : ConsumeFilter[]; get_filters() : ConsumeFilter[];
unregister_filter(filter: ConsumeFilter); unregister_filter(filter: ConsumeFilter);

View File

@ -1,6 +1,6 @@
{ {
"name": "TeaClient", "name": "TeaClient",
"version": "1.5.0-6", "version": "1.5.0-7",
"description": "", "description": "",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {