From f3add08a9238f299ad6f5394b262584b28627228 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Fri, 21 Aug 2020 13:37:10 +0200 Subject: [PATCH] Some audio fixes --- jenkins/create_build.sh | 4 +- modules/core/app-updater/index.ts | 7 +- modules/core/main_window.ts | 7 +- modules/core/render-backend/index.ts | 2 +- modules/core/ui-loader/graphical.ts | 7 +- modules/core/url-preview/index.ts | 6 +- modules/renderer/ExternalModalHandler.ts | 57 +- modules/renderer/UnloadHandler.ts | 60 ++ modules/renderer/audio/AudioFilter.ts | 243 ++++++++ modules/renderer/audio/AudioRecorder.ts | 518 +++++------------- modules/renderer/audio/InputDeviceList.ts | 69 +++ .../renderer/backend-impl/audio/recorder.ts | 8 - .../renderer/connection/ServerConnection.ts | 21 +- .../renderer/connection/VoiceConnection.ts | 18 +- modules/renderer/hooks/AudioInput.ts | 20 + modules/renderer/hooks/ExternalModal.ts | 12 + modules/renderer/hooks/ServerConnection.ts | 25 + modules/renderer/index.ts | 24 +- modules/{core => shared}/window.ts | 49 +- native/CMakeLists.txt | 9 +- native/cmake/nodejs-config.cmake | 1 - native/ppt/CMakeLists.txt | 6 +- native/serverconnection/CMakeLists.txt | 7 +- native/serverconnection/exports/exports.d.ts | 3 +- .../serverconnection/src/audio/AudioGain.cpp | 3 +- .../serverconnection/src/audio/AudioInput.cpp | 177 +----- .../serverconnection/src/audio/AudioInput.h | 14 +- .../src/audio/js/AudioRecorder.cpp | 34 +- .../src/audio/js/AudioRecorder.h | 84 ++- .../src/connection/audio/AudioSender.h | 2 +- .../src/connection/audio/VoiceConnection.cpp | 92 ++-- .../src/connection/audio/VoiceConnection.h | 22 +- package.json | 2 +- 33 files changed, 823 insertions(+), 790 deletions(-) create mode 100644 modules/renderer/UnloadHandler.ts create mode 100644 modules/renderer/audio/AudioFilter.ts create mode 100644 modules/renderer/audio/InputDeviceList.ts delete mode 100644 modules/renderer/backend-impl/audio/recorder.ts create mode 100644 modules/renderer/hooks/AudioInput.ts create mode 100644 modules/renderer/hooks/ExternalModal.ts create mode 100644 modules/renderer/hooks/ServerConnection.ts rename modules/{core => shared}/window.ts (65%) diff --git a/jenkins/create_build.sh b/jenkins/create_build.sh index e6f0dd3..08ef2e3 100755 --- a/jenkins/create_build.sh +++ b/jenkins/create_build.sh @@ -121,6 +121,6 @@ function deploy_client() { #install_npm #compile_scripts -compile_native -package_client +#compile_native +#package_client deploy_client diff --git a/modules/core/app-updater/index.ts b/modules/core/app-updater/index.ts index c88698d..ad4a756 100644 --- a/modules/core/app-updater/index.ts +++ b/modules/core/app-updater/index.ts @@ -20,9 +20,9 @@ import {Arguments, process_args} from "../../shared/process-arguments"; import * as electron from "electron"; import {PassThrough} from "stream"; import ErrnoException = NodeJS.ErrnoException; -import * as winmgr from "../window"; import {reference_app} from "../main_window"; import * as url from "url"; +import {loadWindowBounds, startTrackWindowBounds} from "../../shared/window"; const is_debug = false; export function server_url() : string { @@ -697,9 +697,10 @@ export async function execute_graphical(channel: string, ask_install: boolean) : window.webContents.openDevTools(); } await new Promise(resolve => window.on('ready-to-show', resolve)); + await loadWindowBounds('update-installer', window); + startTrackWindowBounds('update-installer', window); + window.show(); - await winmgr.apply_bounds('update-installer', window); - winmgr.track_bounds('update-installer', window); const current_vers = await current_version(); console.log("Current version: " + current_vers.toString(true)); diff --git a/modules/core/main_window.ts b/modules/core/main_window.ts index 8110756..e5d9fcc 100644 --- a/modules/core/main_window.ts +++ b/modules/core/main_window.ts @@ -1,6 +1,4 @@ import {BrowserWindow, Menu, MenuItem, MessageBoxOptions, app, dialog} from "electron"; -import * as electron from "electron"; -import * as winmgr from "./window"; import * as path from "path"; let app_references = 0; @@ -21,6 +19,7 @@ import * as updater from "./app-updater"; import * as loader from "./ui-loader"; import * as crash_handler from "../crash_handler"; import * as url from "url"; +import {loadWindowBounds, startTrackWindowBounds} from "../shared/window"; // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. @@ -62,8 +61,8 @@ function spawn_main_window(entry_point: string) { main_window.once('ready-to-show', () => { main_window.show(); - winmgr.apply_bounds('main-window', main_window).then(() => { - winmgr.track_bounds('main-window', main_window); + loadWindowBounds('main-window', main_window).then(() => { + startTrackWindowBounds('main-window', main_window); main_window.focus(); loader.ui.cleanup(); diff --git a/modules/core/render-backend/index.ts b/modules/core/render-backend/index.ts index a05f974..cca6e8e 100644 --- a/modules/core/render-backend/index.ts +++ b/modules/core/render-backend/index.ts @@ -23,4 +23,4 @@ ipcMain.on('basic-action', (event, action, ...args: any[]) => { } else if(action === "reload-window") { window.reload(); } -}); +}); \ No newline at end of file diff --git a/modules/core/ui-loader/graphical.ts b/modules/core/ui-loader/graphical.ts index 999f025..fbdc84b 100644 --- a/modules/core/ui-loader/graphical.ts +++ b/modules/core/ui-loader/graphical.ts @@ -5,8 +5,8 @@ import {screen} from "electron"; import {Arguments, process_args} from "../../shared/process-arguments"; import * as loader from "./loader"; import * as updater from "../app-updater"; -import * as winmgr from "../window"; import * as url from "url"; +import {loadWindowBounds, startTrackWindowBounds} from "../../shared/window"; export namespace ui { let gui: electron.BrowserWindow; @@ -124,8 +124,9 @@ export namespace ui { console.log("Setting UI position to %ox%o", x, y); if(typeof x === "number" && typeof y === "number") gui.setPosition(x, y); - winmgr.apply_bounds('ui-load-window', gui, undefined, { apply_size: false }).then(() => { - winmgr.track_bounds('ui-load-window', gui); + + loadWindowBounds('ui-load-window', gui, undefined, { applySize: false }).then(() => { + startTrackWindowBounds('ui-load-window', gui); const call_loader = () => load_files().catch(reject); if(!process_args.has_flag(...Arguments.DISABLE_ANIMATION)) diff --git a/modules/core/url-preview/index.ts b/modules/core/url-preview/index.ts index ba1fe79..d795d59 100644 --- a/modules/core/url-preview/index.ts +++ b/modules/core/url-preview/index.ts @@ -1,6 +1,6 @@ import * as electron from "electron"; import * as path from "path"; -import * as winmgr from "../window"; +import {loadWindowBounds, startTrackWindowBounds} from "../../shared/window"; let global_window: electron.BrowserWindow; let global_window_promise: Promise; @@ -49,8 +49,8 @@ export async function open_preview(url: string) { }); try { - await winmgr.apply_bounds('url-preview', global_window); - winmgr.track_bounds('url-preview', global_window); + await loadWindowBounds('url-preview', global_window); + startTrackWindowBounds('url-preview', global_window); await new Promise((resolve, reject) => { const timeout = setTimeout(() => reject("timeout"), 5000); diff --git a/modules/renderer/ExternalModalHandler.ts b/modules/renderer/ExternalModalHandler.ts index 9e8d998..48effc7 100644 --- a/modules/renderer/ExternalModalHandler.ts +++ b/modules/renderer/ExternalModalHandler.ts @@ -1,15 +1,15 @@ import {AbstractExternalModalController} from "tc-shared/ui/react-elements/external-modal/Controller"; -import {setExternalModalControllerFactory} from "tc-shared/ui/react-elements/external-modal"; import * as ipc from "tc-shared/ipc/BrowserIPC"; import * as log from "tc-shared/log"; import {LogCategory} from "tc-shared/log"; -import * as loader from "tc-loader"; -import {Stage} from "tc-loader"; import {BrowserWindow, remote} from "electron"; import {tr} from "tc-shared/i18n/localize"; import * as path from "path"; +import {Arguments, process_args} from "../shared/process-arguments"; +import {Popout2ControllerMessages, PopoutIPCMessage} from "tc-shared/ui/react-elements/external-modal/IPCMessage"; +import {loadWindowBounds, startTrackWindowBounds} from "../shared/window"; -class ExternalModalController extends AbstractExternalModalController { +export class ExternalModalController extends AbstractExternalModalController { private window: BrowserWindow; constructor(a, b, c) { @@ -26,21 +26,33 @@ class ExternalModalController extends AbstractExternalModalController { autoHideMenuBar: true, webPreferences: { - nodeIntegration: true + nodeIntegration: true, }, icon: path.join(__dirname, "..", "..", "resources", "logo.ico"), minWidth: 600, - minHeight: 300 + minHeight: 300, + + frame: false, + transparent: true, + + show: true }); + loadWindowBounds("modal-" + this.modalType, this.window).then(() => { + startTrackWindowBounds("modal-" + this.modalType, this.window); + }); + + if(process_args.has_flag(Arguments.DEV_TOOLS)) + this.window.webContents.openDevTools(); + const parameters = { "loader-target": "manifest", "chunk": "modal-external", "modal-target": this.modalType, "ipc-channel": this.ipcChannel.channelId, "ipc-address": ipc.getInstance().getLocalAddress(), - //"disableGlobalContextMenu": is_debug ? 1 : 0, - //"loader-abort": is_debug ? 1 : 0, + "loader-abort": 0, + "animation-short": 1 }; const baseUrl = location.origin + location.pathname + "?"; @@ -54,7 +66,6 @@ class ExternalModalController extends AbstractExternalModalController { return false; } - this.window.show(); this.window.on("closed", () => { this.window = undefined; this.handleWindowClosed(); @@ -73,12 +84,26 @@ class ExternalModalController extends AbstractExternalModalController { protected focusWindow(): void { this.window?.focus(); } -} -loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { - priority: 50, - name: "external modal controller factory setup", - function: async () => { - setExternalModalControllerFactory((modal, events, userData) => new ExternalModalController(modal, events, userData)); + protected handleTypedIPCMessage(type: T, payload: PopoutIPCMessage[T]) { + super.handleTypedIPCMessage(type, payload); + + switch (type) { + case "invoke-modal-action": + const data = payload as PopoutIPCMessage["invoke-modal-action"]; + switch (data.action) { + case "close": + this.destroy(); + break; + + case "minimize": + this.window?.minimize(); + break; + } + break; + + case "hello-popout": + break; + } } -}); \ No newline at end of file +} \ No newline at end of file diff --git a/modules/renderer/UnloadHandler.ts b/modules/renderer/UnloadHandler.ts new file mode 100644 index 0000000..7d7a380 --- /dev/null +++ b/modules/renderer/UnloadHandler.ts @@ -0,0 +1,60 @@ +import {Settings, settings} from "tc-shared/settings"; +import {server_connections} from "tc-shared/ui/frames/connection_handlers"; +import {tr} from "tc-shared/i18n/localize"; +import {Arguments, process_args} from "../shared/process-arguments"; +import {remote} from "electron"; + +const unloadListener = event => { + if(settings.static(Settings.KEY_DISABLE_UNLOAD_DIALOG)) + return; + + const active_connections = server_connections.all_connections().filter(e => e.connected); + if(active_connections.length == 0) return; + + const do_exit = (closeWindow: boolean) => { + const dp = server_connections.all_connections().map(e => { + if(e.serverConnection.connected()) + return e.serverConnection.disconnect(tr("client closed")) + .catch(error => { + console.warn(tr("Failed to disconnect from server %s on client close: %o"), + e.serverConnection.remote_address().host + ":" + e.serverConnection.remote_address().port, + error + ); + }); + return Promise.resolve(); + }); + + if(closeWindow) { + const exit = () => { + const {remote} = window.require('electron'); + remote.getCurrentWindow().close(); + }; + + Promise.all(dp).then(exit); + /* force exit after 2500ms */ + setTimeout(exit, 2500); + } + }; + + if(process_args.has_flag(Arguments.DEBUG)) { + do_exit(false); + return; + } + + remote.dialog.showMessageBox(remote.getCurrentWindow(), { + type: 'question', + buttons: ['Yes', 'No'], + title: 'Confirm', + message: 'Are you really sure?\nYou\'re still connected!' + }).then(result => { + if(result.response === 0) { + /* prevent quitting because we try to disconnect */ + window.removeEventListener("beforeunload", unloadListener); + do_exit(true); + } + }); + + event.preventDefault(); +} + +window.addEventListener("beforeunload", unloadListener); \ No newline at end of file diff --git a/modules/renderer/audio/AudioFilter.ts b/modules/renderer/audio/AudioFilter.ts new file mode 100644 index 0000000..2a6704f --- /dev/null +++ b/modules/renderer/audio/AudioFilter.ts @@ -0,0 +1,243 @@ +import {audio} from "tc-native/connection"; +import {FilterType, StateFilter, ThresholdFilter, VoiceLevelFilter} from "tc-shared/voice/Filter"; +import {NativeInput} from "./AudioRecorder"; + +export abstract class NativeFilter { + readonly priority: number; + + handle: NativeInput; + enabled: boolean = false; + + protected constructor(handle, priority: number) { + this.handle = handle; + this.priority = priority; + } + + abstract initialize(); + abstract finalize(); + + isEnabled(): boolean { + return this.enabled; + } + + setEnabled(flag: boolean): void { + if(this.enabled === flag) + return; + + this.enabled = flag; + + if(this.enabled) { + this.initialize(); + } else { + this.finalize(); + } + } +} + +export class NThresholdFilter extends NativeFilter implements ThresholdFilter { + static readonly frames_per_second = 1 / (960 / 48000); + + readonly type: FilterType.THRESHOLD; + private filter: audio.record.ThresholdConsumeFilter; + + private _margin_frames: number = 25; /* 120ms */ + private _threshold: number = 50; + private _callback_level: any; + + private _attack_smooth = 0; + private _release_smooth = 0; + + private levelCallbacks: ((level: number) => void)[] = []; + + constructor(handle, priority: number) { + super(handle, priority); + + Object.defineProperty(this, 'callback_level', { + get(): any { + return this._callback_level; + }, set(v: any): void { + if(v === this._callback_level) + return; + + this._callback_level = v; + if(this.filter) + this.filter.set_analyze_filter(v); + }, + enumerable: true, + configurable: false, + }) + } + + getMarginFrames(): number { + return this.filter ? this.filter.get_margin_time() * NThresholdFilter.frames_per_second : this._margin_frames; + } + + getThreshold(): number { + return this.filter ? this.filter.get_threshold() : this._threshold; + } + + setMarginFrames(value: number) { + this._margin_frames = value; + if(this.filter) + this.filter.set_margin_time(value / 960 / 1000); + } + + getAttackSmooth(): number { + return this.filter ? this.filter.get_attack_smooth() : this._attack_smooth; + } + + getReleaseSmooth(): number { + return this.filter ? this.filter.get_release_smooth() : this._release_smooth; + } + + setAttackSmooth(value: number) { + this._attack_smooth = value; + if(this.filter) + this.filter.set_attack_smooth(value); + } + + setReleaseSmooth(value: number) { + this._release_smooth = value; + if(this.filter) + this.filter.set_release_smooth(value); + } + + setThreshold(value: number): Promise { + if(typeof(value) === "string") + value = parseInt(value); /* yes... this happens */ + this._threshold = value; + if(this.filter) + this.filter.set_threshold(value); + return Promise.resolve(); + } + + finalize() { + if(this.filter) { + if(this.handle.getNativeConsumer()) + this.handle.getNativeConsumer().unregister_filter(this.filter); + this.filter = undefined; + } + } + + initialize() { + const consumer = this.handle.getNativeConsumer(); + 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.set_attack_smooth(this._attack_smooth); + this.filter.set_release_smooth(this._release_smooth); + } + + registerLevelCallback(callback: (value: number) => void) { + this.levelCallbacks.push(callback); + } + + removeLevelCallback(callback: (value: number) => void) { + const index = this.levelCallbacks.indexOf(callback); + if(index === -1) return; + + this.levelCallbacks.splice(index, 1); + } +} + +export class NStateFilter extends NativeFilter implements StateFilter { + readonly type: FilterType.STATE; + private filter: audio.record.StateConsumeFilter; + private active = false; + + constructor(handle, priority: number) { + super(handle, priority); + } + + finalize() { + if(this.filter) { + const consumer = this.handle.getNativeConsumer(); + consumer?.unregister_filter(this.filter); + this.filter = undefined; + } + } + + initialize() { + const consumer = this.handle.getNativeConsumer(); + if(!consumer) + return; + + this.finalize(); + this.filter = consumer.create_filter_state(); + this.filter.set_consuming(this.active); + } + + isActive(): boolean { + return this.active; + } + + setState(state: boolean) { + if(this.active === state) + return; + this.active = state; + if(this.filter) { + this.filter.set_consuming(state); + } + } +} + +export class NVoiceLevelFilter extends NativeFilter implements VoiceLevelFilter { + static readonly frames_per_second = 1 / (960 / 48000); + + readonly type: FilterType.VOICE_LEVEL; + private filter: audio.record.VADConsumeFilter; + private level = 3; + private _margin_frames = 6; + + constructor(handle, priority: number) { + super(handle, priority); + } + + finalize() { + if(this.filter) { + const consumer = this.handle.getNativeConsumer(); + consumer?.unregister_filter(this.filter); + this.filter = undefined; + } + } + + initialize() { + const consumer = this.handle.getNativeConsumer(); + if(!consumer) + return; + + this.finalize(); + this.filter = consumer.create_filter_vad(this.level); + this.filter.set_margin_time(this._margin_frames / NVoiceLevelFilter.frames_per_second); + } + + getLevel(): number { + return this.level; + } + + setLevel(value: number) { + if(this.level === value) + return; + + this.level = value; + if(this.filter) { + this.finalize(); + this.initialize(); + } + } + + setMarginFrames(value: number) { + this._margin_frames = value; + if(this.filter) + this.filter.set_margin_time(value / NVoiceLevelFilter.frames_per_second); + } + + getMarginFrames(): number { + return this.filter ? this.filter.get_margin_time() * NVoiceLevelFilter.frames_per_second : this._margin_frames; + } +} \ No newline at end of file diff --git a/modules/renderer/audio/AudioRecorder.ts b/modules/renderer/audio/AudioRecorder.ts index 165761b..783bd25 100644 --- a/modules/renderer/audio/AudioRecorder.ts +++ b/modules/renderer/audio/AudioRecorder.ts @@ -1,445 +1,188 @@ import { - filter, AbstractInput, - InputDevice, - InputState, InputConsumer, - InputConsumerType, InputStartResult, LevelMeter + InputConsumerType, + InputEvents, + InputStartResult, + InputState, + LevelMeter } from "tc-shared/voice/RecorderBase"; import {audio} from "tc-native/connection"; import {tr} from "tc-shared/i18n/localize"; - -interface NativeDevice extends InputDevice { - device_index: number; - native: any; -} - -let _device_cache: NativeDevice[] = undefined; -export function devices() : InputDevice[] { - //TODO: Handle device updates! - if(!audio.initialized()) return []; - - return _device_cache || (_device_cache = audio.available_devices().filter(e => e.input_supported || e.input_default).map(e => { - return { - unique_id: e.device_id, - channels: 2, /* TODO */ - default_input: e.input_default, - supported: e.input_supported, - name: e.name, - driver: e.driver, - sample_rate: 48000, /* TODO! */ - native: e - } as NativeDevice - })); -} - -export function device_refresh_available() : boolean { return false; } -export function refresh_devices() : Promise { throw "not supported yet!"; } - -export function create_input() : AbstractInput { - return new NativeInput(); -} - -namespace filters { - export abstract class NativeFilter implements filter.Filter { - type: filter.Type; - handle: NativeInput; - enabled: boolean = false; - - protected constructor(handle, type) { this.handle = handle; this.type = type; } - - abstract initialize(); - abstract finalize(); - - - is_enabled(): boolean { return this.enabled; } - } - - export class NThresholdFilter extends NativeFilter implements filter.ThresholdFilter { - static readonly frames_per_second = 1 / (960 / 48000); - private filter: audio.record.ThresholdConsumeFilter; - - private _margin_frames: number = 25; /* 120ms */ - private _threshold: number = 50; - private _callback_level: any; - - private _attack_smooth = 0; - private _release_smooth = 0; - - callback_level: (level: number) => any; - - constructor(handle) { - super(handle, filter.Type.THRESHOLD); - - Object.defineProperty(this, 'callback_level', { - get(): any { - return this._callback_level; - }, set(v: any): void { - if(v === this._callback_level) - return; - - this._callback_level = v; - if(this.filter) - this.filter.set_analyze_filter(v); - }, - enumerable: true, - configurable: false, - }) - } - - get_margin_frames(): number { - return this.filter ? this.filter.get_margin_time() * NThresholdFilter.frames_per_second : this._margin_frames; - } - - get_threshold(): number { - return this.filter ? this.filter.get_threshold() : this._threshold; - } - - set_margin_frames(value: number) { - this._margin_frames = value; - if(this.filter) - this.filter.set_margin_time(value / 960 / 1000); - } - - get_attack_smooth(): number { - return this.filter ? this.filter.get_attack_smooth() : this._attack_smooth; - } - - get_release_smooth(): number { - return this.filter ? this.filter.get_release_smooth() : this._release_smooth; - } - - set_attack_smooth(value: number) { - this._attack_smooth = value; - if(this.filter) - this.filter.set_attack_smooth(value); - } - - set_release_smooth(value: number) { - this._release_smooth = value; - if(this.filter) - this.filter.set_release_smooth(value); - } - - set_threshold(value: number): Promise { - if(typeof(value) === "string") - value = parseInt(value); /* yes... this happens */ - this._threshold = value; - if(this.filter) - this.filter.set_threshold(value); - return Promise.resolve(); - } - - finalize() { - if(this.filter) { - if(this.handle.consumer) - this.handle.consumer.unregister_filter(this.filter); - this.filter = undefined; - } - } - - initialize() { - if(!this.handle.consumer) - return; - - this.finalize(); - this.filter = this.handle.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.set_attack_smooth(this._attack_smooth); - this.filter.set_release_smooth(this._release_smooth); - } - } - - export class NStateFilter extends NativeFilter implements filter.StateFilter { - private filter: audio.record.StateConsumeFilter; - private active = false; - - constructor(handle) { - super(handle, filter.Type.STATE); - } - - finalize() { - if(this.filter) { - if(this.handle.consumer) - this.handle.consumer.unregister_filter(this.filter); - this.filter = undefined; - } - } - - initialize() { - if(!this.handle.consumer) - return; - - this.finalize(); - this.filter = this.handle.consumer.create_filter_state(); - this.filter.set_consuming(this.active); - } - - is_active(): boolean { - return this.active; - } - - async set_state(state: boolean): Promise { - if(this.active === state) - return; - this.active = state; - if(this.filter) - this.filter.set_consuming(state); - } - } - - export class NVoiceLevelFilter extends NativeFilter implements filter.VoiceLevelFilter { - static readonly frames_per_second = 1 / (960 / 48000); - private filter: audio.record.VADConsumeFilter; - private level = 3; - private _margin_frames = 6; - - constructor(handle) { - super(handle, filter.Type.VOICE_LEVEL); - } - - finalize() { - if(this.filter) { - if(this.handle.consumer) - this.handle.consumer.unregister_filter(this.filter); - this.filter = undefined; - } - } - - initialize() { - if(!this.handle.consumer) - return; - - this.finalize(); - this.filter = this.handle.consumer.create_filter_vad(this.level); - this.filter.set_margin_time(this._margin_frames / NVoiceLevelFilter.frames_per_second); - } - - get_level(): number { - return this.level; - } - - set_level(value: number) { - if(this.level === value) - return; - - this.level = value; - if(this.filter) { - this.finalize(); - this.initialize(); - } - } - - set_margin_frames(value: number) { - this._margin_frames = value; - if(this.filter) - this.filter.set_margin_time(value / NVoiceLevelFilter.frames_per_second); - } - - get_margin_frames(): number { - return this.filter ? this.filter.get_margin_time() * NVoiceLevelFilter.frames_per_second : this._margin_frames; - } - } -} +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"; export class NativeInput implements AbstractInput { - private handle: audio.record.AudioRecorder; - consumer: audio.record.AudioConsumer; + readonly events: Registry; - private _current_device: InputDevice; - private _current_state: InputState = InputState.PAUSED; + private nativeHandle: audio.record.AudioRecorder; + private nativeConsumer: audio.record.AudioConsumer; - callback_begin: () => any; - callback_end: () => any; + private state: InputState; + private deviceId: string | undefined; - private filters: filters.NativeFilter[] = []; + private registeredFilters: (Filter & NativeFilter)[] = []; + private filtered = false; constructor() { - this.handle = audio.record.create_recorder(); + this.events = new Registry(); - this.consumer = this.handle.create_consumer(); - this.consumer.callback_ended = () => { - if(this._current_state !== InputState.RECORDING) - return; + this.nativeHandle = audio.record.create_recorder(); - this._current_state = InputState.DRY; - if(this.callback_end) - this.callback_end(); + this.nativeConsumer = this.nativeHandle.create_consumer(); + this.nativeConsumer.callback_ended = () => { + this.filtered = true; + this.events.fire("notify_voice_end"); }; - this.consumer.callback_started = () => { - if(this._current_state !== InputState.DRY) - return; - - this._current_state = InputState.RECORDING; - if(this.callback_begin) - this.callback_begin(); + this.nativeConsumer.callback_started = () => { + this.filtered = false; + this.events.fire("notify_voice_start"); }; - this._current_state = InputState.PAUSED; + this.state = InputState.PAUSED; } - /* TODO: some kind of finalize? */ - current_consumer(): InputConsumer | undefined { - return { - type: InputConsumerType.NATIVE - }; + async start(): Promise { + if(this.state === InputState.RECORDING) { + logWarn(LogCategory.VOICE, tr("Tried to start an input recorder twice.")); + return InputStartResult.EOK; + } + + this.state = InputState.INITIALIZING; + try { + const state = await new Promise(resolve => this.nativeHandle.set_device(this.deviceId, resolve)); + + if(state !== "success") { + if(state === "invalid-device") { + return InputStartResult.EDEVICEUNKNOWN; + } else if(state === undefined) { + throw tr("invalid set device result state"); + } + throw state; + } + + await new Promise((resolve, reject) => this.nativeHandle.start(result => { + if(result === true) { + resolve(); + } else { + reject(typeof result === "string" ? result : tr("failed to start input")); + } + })); + + this.state = InputState.RECORDING; + return InputStartResult.EOK; + } finally { + if(this.state === InputState.INITIALIZING) { + this.state = InputState.PAUSED; + } + } } - async set_consumer(consumer: InputConsumer): Promise { - if(typeof(consumer) !== "undefined") - throw "we only support native consumers!"; /* TODO: May create a general wrapper? */ - return; - } - - async set_device(_device: InputDevice | undefined): Promise { - if(_device === this._current_device) + async stop(): Promise { + if(this.state === InputState.PAUSED) return; - this._current_device = _device; - try { - await new Promise(resolve => this.handle.set_device(this._current_device ? this._current_device.unique_id : undefined, resolve)); - if(this._current_state !== InputState.PAUSED && this._current_device) - await new Promise((resolve, reject) => { - this.handle.start(flag => { - if(typeof flag === "boolean" && flag) - resolve(); - else - reject(typeof flag === "string" ? flag : "failed to start"); - }); - }); - } catch(error) { - console.warn(tr("Failed to start playback on new input device (%o)"), error); - throw error; - } + this.nativeHandle.stop(); + this.state = InputState.PAUSED; } - current_device(): InputDevice | undefined { - return this._current_device; + async setDeviceId(device: string | undefined): Promise { + if(this.deviceId === device) + return; + + this.deviceId = device; + await this.stop(); } - current_state(): InputState { - return this._current_state; + currentDeviceId(): string | undefined { + return this.deviceId; } - disable_filter(type: filter.Type) { - const filter = this.get_filter(type) as filters.NativeFilter; - if(filter.is_enabled()) - filter.enabled = false; - filter.finalize(); + isFiltered(): boolean { + return this.filtered; } - enable_filter(type: filter.Type) { - const filter = this.get_filter(type) as filters.NativeFilter; - if(!filter.is_enabled()) { - filter.enabled = true; - filter.initialize(); - } + removeFilter(filter: Filter) { + const index = this.registeredFilters.indexOf(filter as any); + if(index === -1) return; + + const [ f ] = this.registeredFilters.splice(index, 1); + f.finalize(); } - clear_filter() { - for(const filter of this.filters) { - filter.enabled = false; - filter.finalize(); - } - } - - get_filter(type: filter.Type): filter.Filter | undefined { - for(const filter of this.filters) - if(filter.type === type) - return filter; - - let _filter: filters.NativeFilter; + createFilter(type: T, priority: number): FilterTypeClass { + let filter; switch (type) { - case filter.Type.THRESHOLD: - _filter = new filters.NThresholdFilter(this); + case FilterType.STATE: + filter = new NStateFilter(this, priority); break; - case filter.Type.STATE: - _filter = new filters.NStateFilter(this); + + case FilterType.THRESHOLD: + filter = new NThresholdFilter(this, priority); break; - case filter.Type.VOICE_LEVEL: - _filter = new filters.NVoiceLevelFilter(this); + + case FilterType.VOICE_LEVEL: + filter = new NVoiceLevelFilter(this, priority); break; - default: - throw "this filter isn't supported!"; } - this.filters.push(_filter); - return _filter; + + this.registeredFilters.push(filter); + return filter; } - supports_filter(type: filter.Type) : boolean { + supportsFilter(type: FilterType): boolean { switch (type) { - case filter.Type.THRESHOLD: - case filter.Type.STATE: - case filter.Type.VOICE_LEVEL: + case FilterType.VOICE_LEVEL: + case FilterType.THRESHOLD: + case FilterType.STATE: return true; + default: return false; } } - async start(): Promise { - try { - await this.stop(); - } catch(error) { - console.warn(tr("Failed to stop old record session before start (%o)"), error); - } - - this._current_state = InputState.DRY; - try { - if(this._current_device) - await new Promise((resolve, reject) => { - this.handle.start(flag => { - if(flag) - resolve(); - else - reject("start failed"); - }); - }); - for(const filter of this.filters) - if(filter.is_enabled()) - filter.initialize(); - return InputStartResult.EOK; - } catch(error) { - this._current_state = InputState.PAUSED; - throw error; - } + currentState(): InputState { + return this.state; } - async stop(): Promise { - this.handle.stop(); - for(const filter of this.filters) - filter.finalize(); - if(this.callback_end) - this.callback_end(); - this._current_state = InputState.PAUSED; + currentConsumer(): InputConsumer | undefined { + return { + type: InputConsumerType.NATIVE + }; } - get_volume(): number { - return this.handle.get_volume(); + getNativeConsumer() : audio.record.AudioConsumer { + return this.nativeConsumer; } - set_volume(volume: number) { - this.handle.set_volume(volume); + async setConsumer(consumer: InputConsumer): Promise { + if(typeof(consumer) !== "undefined") + throw "we only support native consumers!"; // TODO: May create a general wrapper? + return; + } + + setVolume(volume: number) { + this.nativeHandle.set_volume(volume); + } + + getVolume(): number { + return this.nativeHandle.get_volume(); } } -export async function create_levelmeter(device: InputDevice) : Promise { - const meter = new NativeLevelmenter(device as any); - await meter.initialize(); - return meter; -} - -class NativeLevelmenter implements LevelMeter { - readonly _device: NativeDevice; +export class NativeLevelMeter implements LevelMeter { + readonly _device: IDevice; private _callback: (num: number) => any; private _recorder: audio.record.AudioRecorder; private _consumer: audio.record.AudioConsumer; private _filter: audio.record.ThresholdConsumeFilter; - constructor(device: NativeDevice) { + constructor(device: IDevice) { this._device = device; } @@ -452,7 +195,7 @@ class NativeLevelmenter implements LevelMeter { this._filter.set_attack_smooth(.75); this._filter.set_release_smooth(.75); - await new Promise(resolve => this._recorder.set_device(this._device ? this._device.unique_id : undefined, resolve)); + await new Promise(resolve => this._recorder.set_device(this._device.deviceId, resolve)); await new Promise((resolve, reject) => { this._recorder.start(flag => { if (typeof flag === "boolean" && flag) @@ -475,22 +218,25 @@ class NativeLevelmenter implements LevelMeter { }); } - destory() { + destroy() { if (this._filter) { this._filter.set_analyze_filter(undefined); this._consumer.unregister_filter(this._filter); } - if (this._consumer) + + if (this._consumer) { this._recorder.delete_consumer(this._consumer); - this._recorder.stop(); - this._recorder.set_device(undefined, () => { - }); /* -1 := No device */ + } + + if(this._recorder) { + this._recorder.stop(); + } this._recorder = undefined; this._consumer = undefined; this._filter = undefined; } - device(): InputDevice { + device(): IDevice { return this._device; } diff --git a/modules/renderer/audio/InputDeviceList.ts b/modules/renderer/audio/InputDeviceList.ts new file mode 100644 index 0000000..e0c685f --- /dev/null +++ b/modules/renderer/audio/InputDeviceList.ts @@ -0,0 +1,69 @@ +import {AbstractDeviceList, DeviceListEvents, IDevice, PermissionState} from "tc-shared/audio/recorder"; +import {Registry} from "tc-shared/events"; +import * as loader from "tc-loader"; + +import {audio} from "tc-native/connection"; + +interface NativeIDevice extends IDevice { + isDefault: boolean +} + +class InputDeviceList extends AbstractDeviceList { + private cachedDevices: NativeIDevice[]; + + constructor() { + super(); + + this.setPermissionState("granted"); + } + + isRefreshAvailable(): boolean { + return false; + } + + async refresh(): Promise { + throw "not supported"; + } + + async requestPermissions(): Promise { + return "granted"; + } + + getDefaultDeviceId(): string { + return this.getDevices().find(e => e.isDefault)?.deviceId || "default"; + } + + getDevices(): NativeIDevice[] { + if(this.cachedDevices) + return this.cachedDevices; + + this.cachedDevices = audio.available_devices() + .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 => { + return { + deviceId: device.device_id, + name: device.name, + driver: device.driver, + isDefault: device.input_default + } + }); + this.setState("healthy"); + return this.cachedDevices; + } + + getEvents(): Registry { + return this.events; + } +} + +export let inputDeviceList; + +loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { + function: async () => { + inputDeviceList = new InputDeviceList(); + inputDeviceList.getDevices(); + }, + priority: 80, + name: "initialize input devices" +}); diff --git a/modules/renderer/backend-impl/audio/recorder.ts b/modules/renderer/backend-impl/audio/recorder.ts deleted file mode 100644 index f169447..0000000 --- a/modules/renderer/backend-impl/audio/recorder.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as handler from "../../audio/AudioRecorder"; - -export const devices = handler.devices; -export const device_refresh_available = handler.device_refresh_available; -export const refresh_devices = handler.refresh_devices; - -export const create_input = handler.create_input; -export const create_levelmeter = handler.create_levelmeter; \ No newline at end of file diff --git a/modules/renderer/connection/ServerConnection.ts b/modules/renderer/connection/ServerConnection.ts index 7257bbb..abbc7e5 100644 --- a/modules/renderer/connection/ServerConnection.ts +++ b/modules/renderer/connection/ServerConnection.ts @@ -2,8 +2,7 @@ import {AbstractCommandHandler, AbstractCommandHandlerBoss} from "tc-shared/conn import { AbstractServerConnection, CommandOptionDefaults, CommandOptions, ConnectionStateListener, - ServerCommand, - voice + ServerCommand } from "tc-shared/connection/ConnectionBase"; import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration"; import {tr} from "tc-shared/i18n/localize"; @@ -13,8 +12,8 @@ import {ConnectionCommandHandler} from "tc-shared/connection/CommandHandler"; import {HandshakeHandler} from "tc-shared/connection/HandshakeHandler"; import {ServerAddress} from "tc-shared/ui/server"; import {TeaSpeakHandshakeHandler} from "tc-shared/profiles/identities/TeamSpeakIdentity"; -import AbstractVoiceConnection = voice.AbstractVoiceConnection; import {VoiceConnection} from "./VoiceConnection"; +import {AbstractVoiceConnection} from "tc-shared/connection/VoiceConnection"; class ErrorCommandHandler extends AbstractCommandHandler { private _handle: ServerConnection; @@ -236,7 +235,7 @@ export class ServerConnection extends AbstractServerConnection { return true; } - voice_connection(): AbstractVoiceConnection { + getVoiceConnection(): AbstractVoiceConnection { return this._voice_connection; } @@ -303,17 +302,3 @@ export class NativeConnectionCommandBoss extends AbstractCommandHandlerBoss { super(connection); } } - - -/* override the "normal" connection */ -export function spawn_server_connection(handle: ConnectionHandler) : AbstractServerConnection { - console.log("Spawning native connection"); - return new ServerConnection(handle); /* will be overridden by the client */ -} - -export function destroy_server_connection(handle: AbstractServerConnection) { - if(!(handle instanceof ServerConnection)) - throw "invalid handle"; - //TODO: Here! - console.log("Call to destroy a server connection"); -} \ No newline at end of file diff --git a/modules/renderer/connection/VoiceConnection.ts b/modules/renderer/connection/VoiceConnection.ts index be20993..c5b6b2a 100644 --- a/modules/renderer/connection/VoiceConnection.ts +++ b/modules/renderer/connection/VoiceConnection.ts @@ -1,13 +1,15 @@ -import {voice} from "tc-shared/connection/ConnectionBase"; -import AbstractVoiceConnection = voice.AbstractVoiceConnection; import {ServerConnection} from "./ServerConnection"; import {NativeVoiceConnection} from "tc-native/connection"; import {RecorderProfile} from "tc-shared/voice/RecorderProfile"; import {tr} from "tc-shared/i18n/localize"; import {LogCategory} from "tc-shared/log"; import * as log from "tc-shared/log"; -import VoiceClient = voice.VoiceClient; -import LatencySettings = voice.LatencySettings; +import { + AbstractVoiceConnection, + LatencySettings, + VoiceClient, + VoiceConnectionStatus +} from "tc-shared/connection/VoiceConnection"; import {NativeInput} from "../audio/AudioRecorder"; export class VoiceConnection extends AbstractVoiceConnection { @@ -56,7 +58,7 @@ export class VoiceConnection extends AbstractVoiceConnection { this.connection.client.update_voice_status(undefined); }; - this.handle.set_audio_source((recorder.input as NativeInput).consumer); + this.handle.set_audio_source((recorder.input as NativeInput).getNativeConsumer()); } this.connection.client.update_voice_status(undefined); } @@ -99,15 +101,15 @@ export class VoiceConnection extends AbstractVoiceConnection { return; } - log.info(LogCategory.VOICE, tr("Local voice started")); + log.info(LogCategory.VOICE, tr("Local voice started (Native)")); this.handle.enable_voice_send(true); const ch = chandler.getClient(); if(ch) ch.speaking = true; } - connected(): boolean { - return true; /* we cant be disconnected at any time! */ + getConnectionState(): VoiceConnectionStatus { + return VoiceConnectionStatus.Connected; } voice_recorder(): RecorderProfile { diff --git a/modules/renderer/hooks/AudioInput.ts b/modules/renderer/hooks/AudioInput.ts new file mode 100644 index 0000000..ecd07b0 --- /dev/null +++ b/modules/renderer/hooks/AudioInput.ts @@ -0,0 +1,20 @@ +import {AudioRecorderBacked, DeviceList, IDevice, setRecorderBackend} from "tc-shared/audio/recorder"; +import {AbstractInput, LevelMeter} from "tc-shared/voice/RecorderBase"; +import {inputDeviceList} from "../audio/InputDeviceList"; +import {NativeInput, NativeLevelMeter} from "../audio/AudioRecorder"; + +setRecorderBackend(new class implements AudioRecorderBacked { + createInput(): AbstractInput { + return new NativeInput(); + } + + async createLevelMeter(device: IDevice): Promise { + const meter = new NativeLevelMeter(device); + await meter.initialize(); + return meter; + } + + getDeviceList(): DeviceList { + return inputDeviceList; + } +}); \ No newline at end of file diff --git a/modules/renderer/hooks/ExternalModal.ts b/modules/renderer/hooks/ExternalModal.ts new file mode 100644 index 0000000..ee0ab12 --- /dev/null +++ b/modules/renderer/hooks/ExternalModal.ts @@ -0,0 +1,12 @@ +import * as loader from "tc-loader"; +import {Stage} from "tc-loader"; +import {setExternalModalControllerFactory} from "tc-shared/ui/react-elements/external-modal"; +import {ExternalModalController} from "../ExternalModalHandler"; + +loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { + priority: 50, + name: "external modal controller factory setup", + function: async () => { + setExternalModalControllerFactory((modal, events, userData) => new ExternalModalController(modal, events, userData)); + } +}); \ No newline at end of file diff --git a/modules/renderer/hooks/ServerConnection.ts b/modules/renderer/hooks/ServerConnection.ts new file mode 100644 index 0000000..c19fa16 --- /dev/null +++ b/modules/renderer/hooks/ServerConnection.ts @@ -0,0 +1,25 @@ +import {ServerConnectionFactory, setServerConnectionFactory} from "tc-shared/connection/ConnectionFactory"; +import {ConnectionHandler} from "tc-shared/ConnectionHandler"; +import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase"; +import * as loader from "tc-loader"; +import {Stage} from "tc-loader"; +import {ServerConnection} from "../connection/ServerConnection"; + +loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { + priority: 50, + name: "server connection factory setup", + function: async () => { + setServerConnectionFactory(new class implements ServerConnectionFactory { + create(client: ConnectionHandler): AbstractServerConnection { + return new ServerConnection(client); + } + + destroy(instance: AbstractServerConnection) { + if(!(instance instanceof ServerConnection)) + throw "invalid handle"; + + instance.finalize(); + } + }); + } +}); \ No newline at end of file diff --git a/modules/renderer/index.ts b/modules/renderer/index.ts index 0eecf5d..169109a 100644 --- a/modules/renderer/index.ts +++ b/modules/renderer/index.ts @@ -28,8 +28,6 @@ declare global { impl_display_critical_error: any; displayCriticalError: any; teaclient_initialize: any; - - open_connected_question: () => Promise; } } @@ -56,8 +54,7 @@ loader.register_task(loader.Stage.INITIALIZING, { loader.register_task(loader.Stage.INITIALIZING, { name: "teaclient initialize logging", function: async () => { - const logger = require("./logger"); - logger.setup(); + (await import("./Logger")).setup(); }, priority: 80 }); @@ -100,19 +97,11 @@ loader.register_task(loader.Stage.INITIALIZING, { if(process_args.has_value(Arguments.DUMMY_CRASH_RENDERER)) crash_handler.handler.crash(); - if(!process_args.has_flag(Arguments.DEBUG)) { - window.open_connected_question = () => remote.dialog.showMessageBox(remote.getCurrentWindow(), { - type: 'question', - buttons: ['Yes', 'No'], - title: 'Confirm', - message: 'Are you really sure?\nYou\'re still connected!' - }).then(result => result.response === 0); - } - /* loader url setup */ { const baseUrl = process_args.value(Arguments.SERVER_URL); - if(typeof baseUrl === "string") { + console.error(process_args.value(Arguments.UPDATER_UI_LOAD_TYPE)); + if(typeof baseUrl === "string" && parseFloat((process_args.value(Arguments.UPDATER_UI_LOAD_TYPE)?.toString() || "").trim()) === 3) { loader.config.baseUrl = baseUrl; } } @@ -160,7 +149,12 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { await import("./SingleInstanceHandler"); await import("./IconHelper"); await import("./connection/FileTransfer"); - await import("./ExternalModalHandler"); + + await import("./hooks/AudioInput"); + await import("./hooks/ExternalModal"); + await import("./hooks/ServerConnection"); + + await import("./UnloadHandler"); } catch (error) { console.log(error); window.displayCriticalError("Failed to load native extensions: " + error); diff --git a/modules/core/window.ts b/modules/shared/window.ts similarity index 65% rename from modules/core/window.ts rename to modules/shared/window.ts index 471eccd..cc8c2df 100644 --- a/modules/core/window.ts +++ b/modules/shared/window.ts @@ -2,27 +2,27 @@ import * as electron from "electron"; import * as fs from "fs-extra"; import * as path from "path"; -/* We read/write to this file every time again because this file could be used by multible processes */ -const data_file: string = path.join(electron.app.getPath('userData'), "window-bounds.json"); +/* We read/write to this file every time again because this file could be used by multiple processes */ +const data_file: string = path.join((electron.app || electron.remote.app).getPath('userData'), "window-bounds.json"); import BrowserWindow = Electron.BrowserWindow; import Rectangle = Electron.Rectangle; -let _changed_data: {[key: string]:Rectangle} = {}; -let _changed_saver: NodeJS.Timer; +let changedData: {[key: string]:Rectangle} = {}; +let changedDataSaveTimeout: NodeJS.Timer; export async function save_changes() { - clearTimeout(_changed_saver); + clearTimeout(changedDataSaveTimeout); try { const data = (await fs.pathExists(data_file) ? await fs.readJson(data_file) : {}) || {}; - Object.assign(data, _changed_data); + Object.assign(data, changedData); await fs.ensureFile(data_file); await fs.writeJson(data_file, data); path_exists = true; - _changed_data = {}; + changedData = {}; } catch(error) { console.warn("Failed to save window bounds: %o", error); } @@ -51,44 +51,47 @@ export async function get_last_bounds(key: string) : Promise { } } -export function track_bounds(key: string, window: BrowserWindow) { +export function startTrackWindowBounds(windowId: string, window: BrowserWindow) { const events = ['move', 'moved', 'resize']; - const update_bounds = () => { - _changed_data[key] = window.getBounds(); + const onWindowBoundsChanged = () => { + changedData[windowId] = window.getBounds(); - clearTimeout(_changed_saver); - _changed_saver = setTimeout(save_changes, 1000); + clearTimeout(changedDataSaveTimeout); + changedDataSaveTimeout = setTimeout(save_changes, 1000); }; for(const event of events) - window.on(event as any, update_bounds); + window.on(event as any, onWindowBoundsChanged); window.on('closed', () => { for(const event of events) - window.removeListener(event as any, update_bounds); - }) + window.removeListener(event as any, onWindowBoundsChanged); + }); } -export async function apply_bounds(key: string, window: BrowserWindow, bounds?: Rectangle, options?: { apply_size?: boolean; apply_position?: boolean }) { - const screen = electron.screen; +export async function loadWindowBounds(windowId: string, window: BrowserWindow, bounds?: Rectangle, options?: { applySize?: boolean; applyPosition?: boolean }) { + const screen = electron.screen || electron.remote.screen; - if(!bounds) - bounds = await get_last_bounds(key); + if(!bounds) { + bounds = await get_last_bounds(windowId); + } - if(!options) + if(!options) { options = {}; + } const original_bounds = window.getBounds(); - if(typeof(options.apply_size) !== "boolean" || options.apply_size) { + if(typeof(options.applySize) !== "boolean" || options.applySize) { let height = bounds.height > 0 ? bounds.height : original_bounds.height; let width = bounds.width > 0 ? bounds.width : original_bounds.width; if(height != original_bounds.height || width != original_bounds.width) window.setSize(width, height, true); } - if(typeof(options.apply_position) !== "boolean" || options.apply_position) { + + if(typeof(options.applyPosition) !== "boolean" || options.applyPosition) { let x = typeof(bounds.x) === "number" ? bounds.x : original_bounds.x; let y = typeof(bounds.y) === "number" ? bounds.y : original_bounds.y; @@ -101,7 +104,7 @@ export async function apply_bounds(key: string, window: BrowserWindow, bounds?: flag_invalid = flag_invalid || bounds.y > x || (bounds.y + bounds.height) < y; if(!flag_invalid) { window.setPosition(x, y, true); - console.log("Updating position for %s", key); + console.log("Updating position for %s", windowId); } } } diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt index d0678f8..73d17e0 100644 --- a/native/CMakeLists.txt +++ b/native/CMakeLists.txt @@ -57,7 +57,13 @@ function(setup_nodejs) function(add_nodejs_module NAME) message("Registering module ${NAME}") _add_nodejs_module(${NAME} ${ARGN}) - target_compile_features(${NAME} PUBLIC cxx_std_17) + + if(MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17") + else() + target_compile_features(${NAME} PUBLIC cxx_std_17) + endif() + set_target_properties(${NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${NODE_LIB_DIRECTORY}/" @@ -108,6 +114,7 @@ if (MSVC) ) foreach(CompilerFlag ${CompilerFlags}) string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}") + string(REGEX REPLACE "/O\\S+($| )" "/02" ${CompilerFlag} "${${CompilerFlag}}") endforeach() #add_compile_options("/MTd") add_compile_options("/EHsc") #We require exception handling diff --git a/native/cmake/nodejs-config.cmake b/native/cmake/nodejs-config.cmake index fcf7249..3217d29 100644 --- a/native/cmake/nodejs-config.cmake +++ b/native/cmake/nodejs-config.cmake @@ -620,7 +620,6 @@ function(add_nodejs_module NAME) CXX_VISIBILITY_PRESET hidden POSITION_INDEPENDENT_CODE TRUE CMAKE_CXX_STANDARD_REQUIRED TRUE - CXX_STANDARD 11 ) # Handle link flag cases properly diff --git a/native/ppt/CMakeLists.txt b/native/ppt/CMakeLists.txt index ba6ba79..05f6402 100644 --- a/native/ppt/CMakeLists.txt +++ b/native/ppt/CMakeLists.txt @@ -3,15 +3,15 @@ set(MODULE_NAME "teaclient_ppt") set(SOURCE_FILES src/KeyboardHook.cpp) if (MSVC) set(SOURCE_FILES ${SOURCE_FILES} src/Win32KeyboardHook.cpp src/Win32KeyboardHookLL.cpp src/Win32KeyboardRawInput.cpp) - add_definitions(-DUSING_UV_SHARED) else() - add_definitions(-DHAVE_X11) set(SOURCE_FILES ${SOURCE_FILES} src/X11KeyboardHook.cpp) endif() add_nodejs_module(${MODULE_NAME} binding.cc ${SOURCE_FILES}) if (WIN32) - target_compile_definitions(${MODULE_NAME} PUBLIC /O2) + target_compile_definitions(${MODULE_NAME} PRIVATE -DUSING_UV_SHARED) +else() + target_compile_definitions(${MODULE_NAME} PRIVATE -DHAVE_X11) endif() add_executable(Hook-Test ${SOURCE_FILES} test/HookTest.cpp) diff --git a/native/serverconnection/CMakeLists.txt b/native/serverconnection/CMakeLists.txt index 38abb18..6fcdb09 100644 --- a/native/serverconnection/CMakeLists.txt +++ b/native/serverconnection/CMakeLists.txt @@ -151,7 +151,7 @@ set(REQUIRED_LIBRARIES spdlog::spdlog_header_only Nan::Helpers - ) +) if (SOUNDIO_BACKED) list(APPEND REQUIRED_LIBRARIES soundio::static) @@ -167,15 +167,12 @@ else() asound jack.a pthread - ) + ) endif() add_definitions(-DNO_OPEN_SSL) target_link_libraries(${MODULE_NAME} ${REQUIRED_LIBRARIES}) target_compile_definitions(${MODULE_NAME} PUBLIC -DNODEJS_API) -if (WIN32) - target_compile_definitions(${MODULE_NAME} PUBLIC /O2) -endif() add_executable(Audio-Test ${SOURCE_FILES} test/audio/main.cpp) target_link_libraries(Audio-Test ${REQUIRED_LIBRARIES}) diff --git a/native/serverconnection/exports/exports.d.ts b/native/serverconnection/exports/exports.d.ts index ba3d668..b274538 100644 --- a/native/serverconnection/exports/exports.d.ts +++ b/native/serverconnection/exports/exports.d.ts @@ -234,9 +234,10 @@ declare module "tc-native/connection" { callback_started: () => any; } + export type DeviceSetResult = "success" | "invalid-device"; export interface AudioRecorder { get_device() : string; - set_device(device_id: string, callback: () => void); /* Recorder needs to be started afterwards */ + set_device(device_id: string, callback: (result: DeviceSetResult) => void); /* Recorder needs to be started afterwards */ start(callback: (result: boolean | string) => void); started() : boolean; diff --git a/native/serverconnection/src/audio/AudioGain.cpp b/native/serverconnection/src/audio/AudioGain.cpp index 9da5533..a9ecb3f 100644 --- a/native/serverconnection/src/audio/AudioGain.cpp +++ b/native/serverconnection/src/audio/AudioGain.cpp @@ -2,6 +2,7 @@ // Created by WolverinDEV on 09/08/2020. // +#include #include "AudioGain.h" #include "../logger.h" @@ -18,7 +19,7 @@ bool tc::audio::apply_gain(void *vp_buffer, size_t channel_count, size_t sample_ if(value > 1.f) { log_debug(category::audio, tr("Audio gain apply clipped: {}"), (float) value); - value = 1.f; + value = isinf(value) ? 0 : 1.f; audio_clipped = true; } } diff --git a/native/serverconnection/src/audio/AudioInput.cpp b/native/serverconnection/src/audio/AudioInput.cpp index 21afc86..1ad5570 100644 --- a/native/serverconnection/src/audio/AudioInput.cpp +++ b/native/serverconnection/src/audio/AudioInput.cpp @@ -11,165 +11,19 @@ using namespace std; using namespace tc; using namespace tc::audio; -#if false -class AudioInputSource { - public: - constexpr static auto kChannelCount{2}; - constexpr static auto kSampleRate{48000}; - - explicit AudioInputSource(PaHostApiIndex index) : device_index{index} {} - ~AudioInputSource() = default; - - /* its blocking! */ - void register_consumer(AudioInput* consumer) { - std::lock_guard lock{this->registered_inputs_lock}; - if(find(this->registered_inputs.begin(), this->registered_inputs.end(), consumer) != this->registered_inputs.end()) - return; - - this->registered_inputs.push_back(consumer); - } - - /* its blocking */ - void remove_consumer(AudioInput* consumer) { - std::lock_guard lock{this->registered_inputs_lock}; - - auto index = find(this->registered_inputs.begin(), this->registered_inputs.end(), consumer); - if(index == this->registered_inputs.end()) - return; - - this->registered_inputs.erase(index); - if(!this->registered_inputs.empty()) - return; - } - - /* this could take a bit longer! */ - bool begin_recording(std::string& error) { - std::lock_guard lock{this->state_lock}; - if(this->state == RECORDING) return true; - - if(this->state != STOPPED) { - if(this->state == DELETED) { - error = "stream has been deleted"; - return false; - } - error = "invalid state"; - return false; - } - - this->current_device = Pa_GetDeviceInfo(this->device_index); - if(!this->current_device) { - error = "failed to get device info"; - return false; - } - - PaStreamParameters parameters{}; - memset(¶meters, 0, sizeof(parameters)); - parameters.channelCount = (int) kChannelCount; - parameters.device = this->device_index; - parameters.sampleFormat = paFloat32; - parameters.suggestedLatency = this->current_device->defaultLowOutputLatency; - auto err = Pa_OpenStream( - &this->input_stream, - ¶meters, - nullptr, - (double) kSampleRate, - paFramesPerBufferUnspecified, - paClipOff, - &AudioInputSource::pa_audio_callback, - this); - - if(err != paNoError) { - this->input_stream = nullptr; - error = to_string(err) + "/" + Pa_GetErrorText(err); - return false; - } - - err = Pa_StartStream(this->input_stream); - if(err != paNoError) { - error = "recording failed " + to_string(err) + "/" + Pa_GetErrorText(err); - err = Pa_CloseStream(this->input_stream); - if(err != paNoError) - log_critical(category::audio, tr("Failed to close opened pa stream. This will cause memory leaks. Error: {}/{}"), err, Pa_GetErrorText(err)); - return false; - } - this->state = RECORDING; - - return true; - } - - void stop_recording_if_possible() { - std::lock_guard lock{this->state_lock}; - if(this->state != RECORDING) return; - - { - std::lock_guard client_lock{this->registered_inputs_lock}; - if(!this->registered_inputs.empty()) return; - } - this->state = STOPPED; - - if(Pa_IsStreamActive(this->input_stream)) - Pa_AbortStream(this->input_stream); - - auto error = Pa_CloseStream(this->input_stream); - if(error != paNoError) - log_error(category::audio, tr("Failed to close PA stream: {}"), error); - this->input_stream = nullptr; - } - - const PaDeviceIndex device_index; - private: - static int pa_audio_callback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* _input_source) { - if(!input) return 0; /* this should never happen */ - auto input_source = (AudioInputSource*) _input_source; - - std::lock_guard lock{input_source->registered_inputs_lock}; - for(auto& client : input_source->registered_inputs) - client->consume(input, frameCount, 2); - return 0; - } - - std::mutex state_lock{}; - enum _state { - STOPPED, - RECORDING, - DELETED - } state{STOPPED}; - PaStream* input_stream{nullptr}; - const PaDeviceInfo* current_device = nullptr; - - std::mutex registered_inputs_lock{}; - std::vector registered_inputs{}; -}; -std::mutex input_sources_lock{}; -static std::deque> input_sources{}; - -std::shared_ptr get_input_source(PaDeviceIndex device_index, bool create = true) { - std::lock_guard sources_lock{input_sources_lock}; - for(const auto& input : input_sources) - if(input->device_index == device_index) - return input; - if(!create) - return nullptr; - - auto input = std::make_shared(device_index); - input_sources.push_back(std::make_shared(device_index)); - return input; -} -#endif - AudioConsumer::AudioConsumer(tc::audio::AudioInput *handle, size_t channel_count, size_t sample_rate, size_t frame_size) : handle(handle), channel_count(channel_count), sample_rate(sample_rate) , frame_size(frame_size) { if(this->frame_size > 0) { - this->reframer = make_unique(channel_count, frame_size); + this->reframer = std::make_unique(channel_count, frame_size); this->reframer->on_frame = [&](const void* buffer) { this->handle_framed_data(buffer, this->frame_size); }; } } void AudioConsumer::handle_framed_data(const void *buffer, size_t samples) { - unique_lock read_callback_lock(this->on_read_lock); + std::unique_lock read_callback_lock(this->on_read_lock); auto function = this->on_read; /* copy */ read_callback_lock.unlock(); if(!function) @@ -189,7 +43,7 @@ AudioInput::AudioInput(size_t channels, size_t rate) : _channel_count(channels), AudioInput::~AudioInput() { this->close_device(); { - lock_guard lock(this->consumers_lock); + std::lock_guard lock(this->consumers_lock); for(const auto& consumer : this->_consumers) consumer->handle = nullptr; } @@ -197,7 +51,7 @@ AudioInput::~AudioInput() { } void AudioInput::set_device(const std::shared_ptr &device) { - lock_guard lock(this->input_source_lock); + std::lock_guard lock(this->input_source_lock); if(device == this->input_device) return; this->close_device(); @@ -205,7 +59,7 @@ void AudioInput::set_device(const std::shared_ptr &device) { } void AudioInput::close_device() { - lock_guard lock(this->input_source_lock); + std::lock_guard lock(this->input_source_lock); if(this->input_recorder) { this->input_recorder->remove_consumer(this); this->input_recorder->stop_if_possible(); @@ -216,12 +70,15 @@ void AudioInput::close_device() { } bool AudioInput::record(std::string& error) { - lock_guard lock(this->input_source_lock); + std::lock_guard lock(this->input_source_lock); if(!this->input_device) { error = "no device"; return false; } - if(this->input_recorder) return true; + + if(this->input_recorder) { + return true; + } this->input_recorder = this->input_device->record(); if(!this->input_recorder) { @@ -255,9 +112,9 @@ void AudioInput::stop() { } std::shared_ptr AudioInput::create_consumer(size_t frame_length) { - auto result = shared_ptr(new AudioConsumer(this, this->_channel_count, this->_sample_rate, frame_length)); + auto result = std::shared_ptr(new AudioConsumer(this, this->_channel_count, this->_sample_rate, frame_length)); { - lock_guard lock(this->consumers_lock); + std::lock_guard lock(this->consumers_lock); this->_consumers.push_back(result); } return result; @@ -265,7 +122,7 @@ std::shared_ptr AudioInput::create_consumer(size_t frame_length) void AudioInput::delete_consumer(const std::shared_ptr &source) { { - lock_guard lock(this->consumers_lock); + std::lock_guard lock(this->consumers_lock); auto it = find(this->_consumers.begin(), this->_consumers.end(), source); if(it != this->_consumers.end()) this->_consumers.erase(it); @@ -314,13 +171,13 @@ void AudioInput::consume(const void *input, size_t frameCount, size_t channels) audio::apply_gain(this->resample_buffer, this->_channel_count, frameCount, this->_volume); } - auto begin = chrono::system_clock::now(); + auto begin = std::chrono::system_clock::now(); for(const auto& consumer : this->consumers()) consumer->process_data(input, frameCount); - auto end = chrono::system_clock::now(); - auto ms = chrono::duration_cast(end - begin).count(); + auto end = std::chrono::system_clock::now(); + auto ms = std::chrono::duration_cast(end - begin).count(); if(ms > 5) { - log_warn(category::audio, tr("Processing of audio input needed {}ms. This could be an issue!"), chrono::duration_cast(end - begin).count()); + log_warn(category::audio, tr("Processing of audio input needed {}ms. This could be an issue!"), std::chrono::duration_cast(end - begin).count()); } } \ No newline at end of file diff --git a/native/serverconnection/src/audio/AudioInput.h b/native/serverconnection/src/audio/AudioInput.h index 716318f..debfdbb 100644 --- a/native/serverconnection/src/audio/AudioInput.h +++ b/native/serverconnection/src/audio/AudioInput.h @@ -39,7 +39,7 @@ namespace tc::audio { class AudioInput : public AudioDeviceRecord::Consumer { friend class ::AudioInputSource; public: - AudioInput(size_t /* channels */, size_t /* rate */); + AudioInput(size_t /* channels */, size_t /* sample rate */); virtual ~AudioInput(); void set_device(const std::shared_ptr& /* device */); @@ -58,10 +58,10 @@ namespace tc::audio { std::shared_ptr create_consumer(size_t /* frame size */); void delete_consumer(const std::shared_ptr& /* source */); - inline size_t channel_count() { return this->_channel_count; } - inline size_t sample_rate() { return this->_sample_rate; } + [[nodiscard]] inline size_t channel_count() const { return this->_channel_count; } + [[nodiscard]] inline size_t sample_rate() const { return this->_sample_rate; } - inline float volume() { return this->_volume; } + [[nodiscard]] inline float volume() const { return this->_volume; } inline void set_volume(float value) { this->_volume = value; } private: void consume(const void *, size_t, size_t) override; @@ -71,15 +71,15 @@ namespace tc::audio { std::mutex consumers_lock; std::deque> _consumers; - std::unique_ptr _resampler{nullptr}; - std::recursive_mutex input_source_lock; + + std::unique_ptr _resampler{nullptr}; std::shared_ptr input_device{}; void* resample_buffer{nullptr}; size_t resample_buffer_size{0}; - float _volume = 1.f; + float _volume{1.f}; std::shared_ptr input_recorder{}; }; diff --git a/native/serverconnection/src/audio/js/AudioRecorder.cpp b/native/serverconnection/src/audio/js/AudioRecorder.cpp index 90a889d..d356cc7 100644 --- a/native/serverconnection/src/audio/js/AudioRecorder.cpp +++ b/native/serverconnection/src/audio/js/AudioRecorder.cpp @@ -149,23 +149,28 @@ NAN_METHOD(AudioRecorderWrapper::_set_device) { return; } + unique_ptr> _callback = make_unique>(info[1].As()); + unique_ptr> _recorder = make_unique>(info.Holder()); + auto call_callback = [call = std::move(_callback), recorder = move(_recorder)](const std::string& status) { + Nan::HandleScope scope; + auto callback_function = call->Get(Nan::GetCurrentContext()->GetIsolate()); + + v8::Local args[1]; + args[0] = Nan::LocalStringUTF8(status); + (void) callback_function->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, args); + + recorder->Reset(); + call->Reset(); + }; + auto device = is_null_device ? nullptr : audio::find_device_by_id(*Nan::Utf8String(info[0]), true); if(!device && !is_null_device) { - Nan::ThrowError("invalid device id"); + call_callback("invalid-device"); return; } - unique_ptr> _callback = make_unique>(info[1].As()); - unique_ptr> _recorder = make_unique>(info.Holder()); - - auto _async_callback = Nan::async_callback([call = std::move(_callback), recorder = move(_recorder)] { - Nan::HandleScope scope; - auto callback_function = call->Get(Nan::GetCurrentContext()->GetIsolate()); - - (void) callback_function->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr); - - recorder->Reset(); - call->Reset(); + auto _async_callback = Nan::async_callback([callback = std::move(call_callback)] { + callback("success"); }).option_destroyed_execute(true); std::thread([_async_callback, input, device]{ @@ -189,10 +194,11 @@ NAN_METHOD(AudioRecorderWrapper::_start) { std::string error{}; v8::Local argv[1]; - if(input->record(error)) + if(input->record(error)) { argv[0] = Nan::New(true); - else + } else { argv[0] = Nan::LocalString(error); + } (void) info[0].As()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, argv); } diff --git a/native/serverconnection/src/audio/js/AudioRecorder.h b/native/serverconnection/src/audio/js/AudioRecorder.h index 4d3f12d..be99bfe 100644 --- a/native/serverconnection/src/audio/js/AudioRecorder.h +++ b/native/serverconnection/src/audio/js/AudioRecorder.h @@ -4,61 +4,59 @@ #include #include -namespace tc { - namespace audio { - class AudioInput; +namespace tc::audio { + class AudioInput; - namespace recorder { - class AudioConsumerWrapper; + namespace recorder { + class AudioConsumerWrapper; - extern NAN_MODULE_INIT(init_js); + extern NAN_MODULE_INIT(init_js); - extern NAN_METHOD(create_recorder); + extern NAN_METHOD(create_recorder); - class AudioRecorderWrapper : public Nan::ObjectWrap { - public: - static NAN_MODULE_INIT(Init); - static NAN_METHOD(NewInstance); - static inline Nan::Persistent & constructor() { - static Nan::Persistent my_constructor; - return my_constructor; - } + class AudioRecorderWrapper : public Nan::ObjectWrap { + public: + static NAN_MODULE_INIT(Init); + static NAN_METHOD(NewInstance); + static inline Nan::Persistent & constructor() { + static Nan::Persistent my_constructor; + return my_constructor; + } - explicit AudioRecorderWrapper(std::shared_ptr /* input */); - ~AudioRecorderWrapper() override; + explicit AudioRecorderWrapper(std::shared_ptr /* input */); + ~AudioRecorderWrapper() override; - static NAN_METHOD(_get_device); - static NAN_METHOD(_set_device); + static NAN_METHOD(_get_device); + static NAN_METHOD(_set_device); - static NAN_METHOD(_start); - static NAN_METHOD(_started); - static NAN_METHOD(_stop); + static NAN_METHOD(_start); + static NAN_METHOD(_started); + static NAN_METHOD(_stop); - static NAN_METHOD(_create_consumer); - static NAN_METHOD(_get_consumers); - static NAN_METHOD(_delete_consumer); + static NAN_METHOD(_create_consumer); + static NAN_METHOD(_get_consumers); + static NAN_METHOD(_delete_consumer); - static NAN_METHOD(_set_volume); - static NAN_METHOD(_get_volume); + static NAN_METHOD(_set_volume); + static NAN_METHOD(_get_volume); - std::shared_ptr create_consumer(); - void delete_consumer(const AudioConsumerWrapper*); + std::shared_ptr create_consumer(); + void delete_consumer(const AudioConsumerWrapper*); - inline std::deque> consumers() { - std::lock_guard lock(this->_consumer_lock); - return this->_consumers; - } + inline std::deque> consumers() { + std::lock_guard lock(this->_consumer_lock); + return this->_consumers; + } - void do_wrap(const v8::Local& /* obj */); + void do_wrap(const v8::Local& /* obj */); - inline void js_ref() { this->Ref(); } - inline void js_unref() { this->Unref(); } - private: - std::shared_ptr _input; + inline void js_ref() { this->Ref(); } + inline void js_unref() { this->Unref(); } + private: + std::shared_ptr _input; - std::mutex _consumer_lock; - std::deque> _consumers; - }; - } - } + std::mutex _consumer_lock; + std::deque> _consumers; + }; + } } \ No newline at end of file diff --git a/native/serverconnection/src/connection/audio/AudioSender.h b/native/serverconnection/src/connection/audio/AudioSender.h index c839a52..aa8991e 100644 --- a/native/serverconnection/src/connection/audio/AudioSender.h +++ b/native/serverconnection/src/connection/audio/AudioSender.h @@ -21,7 +21,7 @@ namespace tc { friend inline std::shared_ptr<_Tp> std::static_pointer_cast(const std::shared_ptr<_Up>& __r) noexcept; friend class VoiceConnection; public: - VoiceSender(VoiceConnection*); + explicit VoiceSender(VoiceConnection*); virtual ~VoiceSender(); codec::value get_codec() { return this->_current_codec; } diff --git a/native/serverconnection/src/connection/audio/VoiceConnection.cpp b/native/serverconnection/src/connection/audio/VoiceConnection.cpp index a853b46..f60a68e 100644 --- a/native/serverconnection/src/connection/audio/VoiceConnection.cpp +++ b/native/serverconnection/src/connection/audio/VoiceConnection.cpp @@ -37,17 +37,17 @@ NAN_MODULE_INIT(VoiceConnectionWrap::Init) { Nan::SetPrototypeMethod(klass, "decoding_supported", VoiceConnectionWrap::_decoding_supported); Nan::SetPrototypeMethod(klass, "encoding_supported", VoiceConnectionWrap::_encoding_supported); - Nan::SetPrototypeMethod(klass, "register_client", VoiceConnectionWrap::_register_client); - Nan::SetPrototypeMethod(klass, "available_clients", VoiceConnectionWrap::_available_clients); - Nan::SetPrototypeMethod(klass, "unregister_client", VoiceConnectionWrap::_unregister_client); + Nan::SetPrototypeMethod(klass, "register_client", VoiceConnectionWrap::register_client); + Nan::SetPrototypeMethod(klass, "available_clients", VoiceConnectionWrap::available_clients); + Nan::SetPrototypeMethod(klass, "unregister_client", VoiceConnectionWrap::unregister_client); - Nan::SetPrototypeMethod(klass, "audio_source", VoiceConnectionWrap::_audio_source); - Nan::SetPrototypeMethod(klass, "set_audio_source", VoiceConnectionWrap::_set_audio_source); + Nan::SetPrototypeMethod(klass, "audio_source", VoiceConnectionWrap::audio_source); + Nan::SetPrototypeMethod(klass, "set_audio_source", VoiceConnectionWrap::set_audio_source); - Nan::SetPrototypeMethod(klass, "get_encoder_codec", VoiceConnectionWrap::_get_encoder_codec); - Nan::SetPrototypeMethod(klass, "set_encoder_codec", VoiceConnectionWrap::_set_encoder_codec); + Nan::SetPrototypeMethod(klass, "get_encoder_codec", VoiceConnectionWrap::get_encoder_codec); + Nan::SetPrototypeMethod(klass, "set_encoder_codec", VoiceConnectionWrap::set_encoder_codec); - Nan::SetPrototypeMethod(klass, "enable_voice_send", VoiceConnectionWrap::_enable_voice_send); + Nan::SetPrototypeMethod(klass, "enable_voice_send", VoiceConnectionWrap::enable_voice_send); constructor().Reset(Nan::GetFunction(klass).ToLocalChecked()); } @@ -81,17 +81,15 @@ NAN_METHOD(VoiceConnectionWrap::_decoding_supported) { info.GetReturnValue().Set(codec >= 4 && codec <= 5); /* ignore SPEX currently :/ */ } -NAN_METHOD(VoiceConnectionWrap::_register_client) { - return ObjectWrap::Unwrap(info.Holder())->register_client(info); -} - NAN_METHOD(VoiceConnectionWrap::register_client) { + auto connection = ObjectWrap::Unwrap(info.Holder()); + if(info.Length() != 1) { Nan::ThrowError("invalid argument count"); return; } auto id = info[0]->Uint32Value(Nan::GetCurrentContext()).FromMaybe(0); - auto handle = this->handle.lock(); + auto handle = connection->handle.lock(); if(!handle) { Nan::ThrowError("handle has been deallocated"); return; @@ -106,14 +104,10 @@ NAN_METHOD(VoiceConnectionWrap::register_client) { info.GetReturnValue().Set(client->js_handle()); } - -NAN_METHOD(VoiceConnectionWrap::_available_clients) { - return ObjectWrap::Unwrap(info.Holder())->available_clients(info); -} - - NAN_METHOD(VoiceConnectionWrap::available_clients) { - auto handle = this->handle.lock(); + auto connection = ObjectWrap::Unwrap(info.Holder()); + + auto handle = connection->handle.lock(); if(!handle) { Nan::ThrowError("handle has been deallocated"); return; @@ -128,20 +122,20 @@ NAN_METHOD(VoiceConnectionWrap::available_clients) { info.GetReturnValue().Set(result); } -NAN_METHOD(VoiceConnectionWrap::_unregister_client) { - return ObjectWrap::Unwrap(info.Holder())->unregister_client(info); -} NAN_METHOD(VoiceConnectionWrap::unregister_client) { + auto connection = ObjectWrap::Unwrap(info.Holder()); + if(info.Length() != 1) { Nan::ThrowError("invalid argument count"); return; } auto id = info[0]->Uint32Value(Nan::GetCurrentContext()).FromMaybe(0); - auto handle = this->handle.lock(); + auto handle = connection->handle.lock(); if(!handle) { Nan::ThrowError("handle has been deallocated"); return; } + auto client = handle->find_client(id); if(!client) { Nan::ThrowError("missing client"); @@ -152,16 +146,14 @@ NAN_METHOD(VoiceConnectionWrap::unregister_client) { handle->delete_client(client); } -NAN_METHOD(VoiceConnectionWrap::_audio_source) { +NAN_METHOD(VoiceConnectionWrap::audio_source) { auto client = ObjectWrap::Unwrap(info.Holder()); info.GetReturnValue().Set(client->_voice_recoder_handle.Get(info.GetIsolate())); } -NAN_METHOD(VoiceConnectionWrap::_set_audio_source) { - ObjectWrap::Unwrap(info.Holder())->set_audio_source(info); -} - NAN_METHOD(VoiceConnectionWrap::set_audio_source) { + auto connection = ObjectWrap::Unwrap(info.Holder()); + if(info.Length() != 1) { Nan::ThrowError("invalid argument count"); return; @@ -173,36 +165,38 @@ NAN_METHOD(VoiceConnectionWrap::set_audio_source) { return; } - if(!this->handle.lock()) { + auto handle = connection->handle.lock(); + if(!handle) { Nan::ThrowError("handle has been deallocated"); return; } - this->release_recorder(); + connection->release_recorder(); if(!info[0]->IsNullOrUndefined()) { - this->_voice_recoder_ptr = ObjectWrap::Unwrap(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked()); - this->_voice_recoder_handle.Reset(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked()); + connection->_voice_recoder_ptr = ObjectWrap::Unwrap(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked()); + connection->_voice_recoder_handle.Reset(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked()); - auto native_consumer = this->_voice_recoder_ptr->native_consumer(); + auto native_consumer = connection->_voice_recoder_ptr->native_consumer(); - weak_ptr weak_handle = this->handle; + weak_ptr weak_handle = handle; auto sample_rate = native_consumer->sample_rate; auto channels = native_consumer->channel_count; - lock_guard read_lock(this->_voice_recoder_ptr->native_read_callback_lock); - this->_voice_recoder_ptr->native_read_callback = [weak_handle, sample_rate, channels](const void* buffer, size_t length) { + lock_guard read_lock(connection->_voice_recoder_ptr->native_read_callback_lock); + connection->_voice_recoder_ptr->native_read_callback = [weak_handle, sample_rate, channels](const void* buffer, size_t length) { auto handle = weak_handle.lock(); if(!handle) { log_warn(category::audio, tr("Missing voice connection handle. Dropping input!")); return; } - shared_ptr sender = handle->voice_sender(); + auto sender = handle->voice_sender(); if(sender) { - if(length > 0 && buffer) - sender->send_data(buffer, length, sample_rate, channels); - else - sender->send_stop(); + if(length > 0 && buffer) { + sender->send_data(buffer, length, sample_rate, channels); + } else { + sender->send_stop(); + } } else { log_warn(category::audio, tr("Missing voice connection audio sender. Dropping input!")); return; @@ -211,9 +205,9 @@ NAN_METHOD(VoiceConnectionWrap::set_audio_source) { } } -NAN_METHOD(VoiceConnectionWrap::_get_encoder_codec) { - auto _this = ObjectWrap::Unwrap(info.Holder()); - auto handle = _this->handle.lock(); +NAN_METHOD(VoiceConnectionWrap::get_encoder_codec) { + auto connection = ObjectWrap::Unwrap(info.Holder()); + auto handle = connection->handle.lock(); if(!handle) { Nan::ThrowError("handle has been deallocated"); return; @@ -222,9 +216,9 @@ NAN_METHOD(VoiceConnectionWrap::_get_encoder_codec) { info.GetReturnValue().Set(handle->get_encoder_codec()); } -NAN_METHOD(VoiceConnectionWrap::_set_encoder_codec) { - auto _this = ObjectWrap::Unwrap(info.Holder()); - auto handle = _this->handle.lock(); +NAN_METHOD(VoiceConnectionWrap::set_encoder_codec) { + auto connection = ObjectWrap::Unwrap(info.Holder()); + auto handle = connection->handle.lock(); if(!handle) { Nan::ThrowError("handle has been deallocated"); return; @@ -239,7 +233,7 @@ NAN_METHOD(VoiceConnectionWrap::_set_encoder_codec) { handle->set_encoder_codec((uint8_t) info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0)); } -NAN_METHOD(VoiceConnectionWrap::_enable_voice_send) { +NAN_METHOD(VoiceConnectionWrap::enable_voice_send) { auto _this = ObjectWrap::Unwrap(info.Holder()); auto handle = _this->handle.lock(); if(!handle) { diff --git a/native/serverconnection/src/connection/audio/VoiceConnection.h b/native/serverconnection/src/connection/audio/VoiceConnection.h index 3d0e216..8c2b2f0 100644 --- a/native/serverconnection/src/connection/audio/VoiceConnection.h +++ b/native/serverconnection/src/connection/audio/VoiceConnection.h @@ -29,7 +29,7 @@ namespace tc { } explicit VoiceConnectionWrap(const std::shared_ptr&); - virtual ~VoiceConnectionWrap(); + ~VoiceConnectionWrap() override; void do_wrap(const v8::Local&); private: @@ -37,20 +37,16 @@ namespace tc { static NAN_METHOD(_encoding_supported); static NAN_METHOD(_decoding_supported); - static NAN_METHOD(_register_client); - NAN_METHOD(register_client); - static NAN_METHOD(_available_clients); - NAN_METHOD(available_clients); - static NAN_METHOD(_unregister_client); - NAN_METHOD(unregister_client); + static NAN_METHOD(register_client); + static NAN_METHOD(available_clients); + static NAN_METHOD(unregister_client); - static NAN_METHOD(_audio_source); - static NAN_METHOD(_set_audio_source); - NAN_METHOD(set_audio_source); + static NAN_METHOD(audio_source); + static NAN_METHOD(set_audio_source); - static NAN_METHOD(_get_encoder_codec); - static NAN_METHOD(_set_encoder_codec); - static NAN_METHOD(_enable_voice_send); + static NAN_METHOD(get_encoder_codec); + static NAN_METHOD(set_encoder_codec); + static NAN_METHOD(enable_voice_send); void release_recorder(); diff --git a/package.json b/package.json index 243f079..9d93ec7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "TeaClient", - "version": "1.4.9", + "version": "1.4.10", "description": "", "main": "main.js", "scripts": {