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;
private filter: audio.record.ThresholdConsumeFilter;
private _margin_frames: number = 25; /* 120ms */
private _threshold: number = 50;
private _callback_level: any;
private marginFrames: number = 25; /* 120ms */
private threshold: number = 50;
private callbackLevel: () => void;
private _attack_smooth = 0;
private _release_smooth = 0;
@ -54,14 +54,15 @@ export class NThresholdFilter extends NativeFilter implements ThresholdFilter {
Object.defineProperty(this, 'callback_level', {
get(): any {
return this._callback_level;
return this.callbackLevel;
}, set(v: any): void {
if(v === this._callback_level)
if(v === this.callbackLevel)
return;
this._callback_level = v;
if(this.filter)
this.callbackLevel = v;
if(this.filter) {
this.filter.set_analyze_filter(v);
}
},
enumerable: true,
configurable: false,
@ -69,15 +70,15 @@ export class NThresholdFilter extends NativeFilter implements ThresholdFilter {
}
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 {
return this.filter ? this.filter.get_threshold() : this._threshold;
return this.filter ? this.filter.get_threshold() : this.threshold;
}
setMarginFrames(value: number) {
this._margin_frames = value;
this.marginFrames = value;
if(this.filter)
this.filter.set_margin_time(value / 960 / 1000);
}
@ -105,7 +106,7 @@ export class NThresholdFilter extends NativeFilter implements ThresholdFilter {
setThreshold(value: number): Promise<void> {
if(typeof(value) === "string")
value = parseInt(value); /* yes... this happens */
this._threshold = value;
this.threshold = value;
if(this.filter)
this.filter.set_threshold(value);
return Promise.resolve();
@ -113,35 +114,49 @@ export class NThresholdFilter extends NativeFilter implements ThresholdFilter {
finalize() {
if(this.filter) {
if(this.handle.getNativeConsumer())
if(this.handle.getNativeConsumer()) {
this.handle.getNativeConsumer().unregister_filter(this.filter);
}
this.filter = undefined;
}
}
initialize() {
const consumer = this.handle.getNativeConsumer();
if(!consumer)
if(!consumer) {
return;
}
this.finalize();
this.filter = consumer.create_filter_threshold(this._threshold);
if(this._callback_level)
this.filter.set_analyze_filter(this._callback_level);
this.filter.set_margin_time(this._margin_frames / NThresholdFilter.frames_per_second);
this.filter = consumer.create_filter_threshold(this.threshold);
this.filter.set_margin_time(this.marginFrames / NThresholdFilter.frames_per_second);
this.filter.set_attack_smooth(this._attack_smooth);
this.filter.set_release_smooth(this._release_smooth);
this.updateAnalyzeFilterCallback();
}
registerLevelCallback(callback: (value: number) => void) {
this.levelCallbacks.push(callback);
this.updateAnalyzeFilterCallback();
}
removeLevelCallback(callback: (value: number) => void) {
const index = this.levelCallbacks.indexOf(callback);
if(index === -1) return;
if(index === -1) {
return;
}
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,
InputConsumerType,
InputEvents,
InputStartError,
InputState,
LevelMeter, MediaStreamRequestResult
LevelMeter,
} from "tc-shared/voice/RecorderBase";
import {audio} from "tc-native/connection";
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 {NativeFilter, NStateFilter, NThresholdFilter, NVoiceLevelFilter} from "./AudioFilter";
import {IDevice} from "tc-shared/audio/recorder";
import {LogCategory, logWarn} from "tc-shared/log";
import NativeFilterMode = audio.record.FilterMode;
import {LogCategory, logTrace, logWarn} from "tc-shared/log";
import {Settings, settings} from "tc-shared/settings";
import NativeFilterMode = audio.record.FilterMode;
export class NativeInput implements AbstractInput {
static readonly instances = [] as NativeInput[];
@ -59,19 +60,30 @@ export class NativeInput implements AbstractInput {
}
}
async start(): Promise<MediaStreamRequestResult | true> {
if(this.state === InputState.RECORDING) {
logWarn(LogCategory.VOICE, tr("Tried to start an input recorder twice."));
return MediaStreamRequestResult.EBUSY;
private setState(newState: InputState) {
if(this.state === newState) {
return;
}
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 {
const state = await new Promise<audio.record.DeviceSetResult>(resolve => this.nativeHandle.set_device(this.deviceId, resolve));
if(state !== "success") {
if(state === "invalid-device") {
return MediaStreamRequestResult.EDEVICEUNKNOWN;
return InputStartError.EDEVICEUNKNOWN;
} else if(state === undefined) {
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;
} finally {
/* @ts-ignore Typescript isn't smart about awaits in try catch blocks */
if(this.state === InputState.INITIALIZING) {
this.state = InputState.PAUSED;
this.setState(InputState.PAUSED);
}
}
}
async stop(): Promise<void> {
if(this.state === InputState.PAUSED)
if(this.state === InputState.PAUSED) {
return;
}
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> {
@ -110,8 +129,15 @@ export class NativeInput implements AbstractInput {
return;
}
try {
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;
await this.stop();
this.events.fire("notify_device_changed", { oldDeviceId, newDeviceId: device });
}
currentDeviceId(): string | undefined {
@ -124,10 +150,12 @@ export class NativeInput implements AbstractInput {
removeFilter(filter: Filter) {
const index = this.registeredFilters.indexOf(filter as any);
if(index === -1) return;
if(index === -1) {
return;
}
const [ f ] = this.registeredFilters.splice(index, 1);
f.finalize();
const [ registeredFilter ] = this.registeredFilters.splice(index, 1);
registeredFilter.finalize();
}
createFilter<T extends FilterType>(type: T, priority: number): FilterTypeClass<T> {
@ -223,7 +251,13 @@ export class NativeInput implements AbstractInput {
break;
}
if(this.nativeConsumer.get_filter_mode() === nativeMode) {
return;
}
const oldMode = this.getFilterMode();
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) {
if (typeof (error) === "string")
if (typeof (error) === "string") {
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)";
}
/* 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 => {
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 {audio} from "tc-native/connection";
import {LogCategory, logTrace} from "tc-shared/log";
interface NativeIDevice extends IDevice {
isDefault: boolean
@ -39,7 +40,9 @@ class InputDeviceList extends AbstractDeviceList {
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.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 => {

View File

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

View File

@ -228,7 +228,6 @@ declare module "tc-native/connection" {
readonly channelCount: 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[];
unregister_filter(filter: ConsumeFilter);

View File

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