Udated a lot of stuff
This commit is contained in:
parent
6b70b8d425
commit
160c6d83db
2
github
2
github
@ -1 +1 @@
|
||||
Subproject commit 0866874725f393c2804f2926687ea3d858d88653
|
||||
Subproject commit 06391c6cdd772c2f83c1387960f7224f7cd9f514
|
@ -22,6 +22,7 @@ import {PassThrough} from "stream";
|
||||
import {prevent_instant_close} from "../../core/main_window";
|
||||
import ErrnoException = NodeJS.ErrnoException;
|
||||
import {EPERM} from "constants";
|
||||
import * as winmgr from "../window";
|
||||
|
||||
const is_debug = false;
|
||||
export function server_url() : string {
|
||||
@ -684,6 +685,8 @@ export async function execute_graphical(channel: string, ask_install: boolean) :
|
||||
}
|
||||
await new Promise(resolve => window.on('ready-to-show', resolve));
|
||||
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));
|
||||
@ -824,7 +827,7 @@ async function check_update(channel: string) {
|
||||
update_question_open = true;
|
||||
dialog.showMessageBox({
|
||||
buttons: ["update now", "remind me later"],
|
||||
title: "Update available",
|
||||
title: "TeaClient: Update available",
|
||||
message:
|
||||
"There is an update available!\n" +
|
||||
"Should we update now?\n" +
|
||||
|
@ -1,5 +1,7 @@
|
||||
import {BrowserWindow, Menu, MenuItem, MessageBoxOptions, app, dialog} from "electron";
|
||||
import * as electron from "electron";
|
||||
import * as winmgr from "./window";
|
||||
|
||||
export let prevent_instant_close: boolean = true;
|
||||
export let is_debug: boolean;
|
||||
export let allow_dev_tools: boolean;
|
||||
@ -102,7 +104,8 @@ function spawn_main_window(entry_point: string) {
|
||||
show: false,
|
||||
webPreferences: {
|
||||
webSecurity: false,
|
||||
nodeIntegrationInWorker: true
|
||||
nodeIntegrationInWorker: true,
|
||||
nodeIntegration: true
|
||||
},
|
||||
});
|
||||
|
||||
@ -124,11 +127,15 @@ function spawn_main_window(entry_point: string) {
|
||||
|
||||
main_window.once('ready-to-show', () => {
|
||||
main_window.show();
|
||||
main_window.focus();
|
||||
loader.ui.cleanup();
|
||||
if(allow_dev_tools && !main_window.webContents.isDevToolsOpened())
|
||||
main_window.webContents.openDevTools();
|
||||
prevent_instant_close = false; /* just to ensure that the client could be unloaded */
|
||||
winmgr.apply_bounds('main-window', main_window).then(() => {
|
||||
winmgr.track_bounds('main-window', main_window);
|
||||
|
||||
main_window.focus();
|
||||
loader.ui.cleanup();
|
||||
if(allow_dev_tools && !main_window.webContents.isDevToolsOpened())
|
||||
main_window.webContents.openDevTools();
|
||||
prevent_instant_close = false; /* just to ensure that the client could be unloaded */
|
||||
});
|
||||
});
|
||||
|
||||
main_window.webContents.on('new-window', (event, url, frameName, disposition, options, additionalFeatures) => {
|
||||
|
@ -6,6 +6,7 @@ import {BrowserWindow, ipcMain as ipc} from "electron";
|
||||
import {Arguments, process_args} from "../../shared/process-arguments";
|
||||
import UserData = forum.UserData;
|
||||
import {main_window} from "../main_window";
|
||||
import * as winmgr from "../window";
|
||||
|
||||
let current_window: BrowserWindow;
|
||||
let _current_data: UserData;
|
||||
@ -64,10 +65,13 @@ export function open_login(enforce: boolean = false) : Promise<UserData> {
|
||||
show: true,
|
||||
parent: main_window,
|
||||
webPreferences: {
|
||||
webSecurity: false
|
||||
webSecurity: false,
|
||||
nodeIntegration: true
|
||||
},
|
||||
});
|
||||
current_window.setMenu(null);
|
||||
winmgr.apply_bounds('forum-manager', current_window);
|
||||
winmgr.track_bounds('forum-manager', current_window);
|
||||
console.log("Main: " + main_window);
|
||||
|
||||
current_window.loadFile(path.join(path.dirname(module.filename), "ui", "index.html"));
|
||||
|
@ -5,6 +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 {main_window} from "../main_window";
|
||||
|
||||
export namespace ui {
|
||||
let gui: electron.BrowserWindow;
|
||||
@ -26,11 +28,12 @@ export namespace ui {
|
||||
|
||||
export function cleanup() {
|
||||
if(gui) {
|
||||
promise = undefined;
|
||||
resolve = undefined;
|
||||
|
||||
gui.destroy();
|
||||
gui = undefined;
|
||||
|
||||
promise = undefined;
|
||||
resolve = undefined;
|
||||
reject = error => {
|
||||
if(error)
|
||||
console.error("Received error from loader after it had been closed... Error: %o", error);
|
||||
@ -71,47 +74,56 @@ export namespace ui {
|
||||
gui.webContents.send('await-update');
|
||||
}
|
||||
|
||||
function spawn_gui(close_callback: () => any) {
|
||||
function spawn_gui() {
|
||||
console.log("Spawn window!");
|
||||
|
||||
const WINDOW_WIDTH = 340;
|
||||
const WINDOW_HEIGHT = 400;
|
||||
|
||||
let bounds = screen.getPrimaryDisplay().bounds;
|
||||
let x = (bounds.width - WINDOW_WIDTH) / 2;
|
||||
let y = (bounds.height - WINDOW_HEIGHT) / 2;
|
||||
|
||||
let dev_tools = false;
|
||||
|
||||
const WINDOW_WIDTH = 340 + (dev_tools ? 1000 : 0);
|
||||
const WINDOW_HEIGHT = 400 + (process.platform == "win32" ? 40 : 0);
|
||||
|
||||
let bounds = screen.getPrimaryDisplay().bounds;
|
||||
let x = bounds.x + (bounds.width - WINDOW_WIDTH) / 2;
|
||||
let y = bounds.y + (bounds.height - WINDOW_HEIGHT) / 2;
|
||||
console.log("Bounds: %o; Move loader window to %ox%o", bounds, x, y);
|
||||
|
||||
gui = new electron.BrowserWindow({
|
||||
width: dev_tools ? WINDOW_WIDTH + 1000 : WINDOW_WIDTH,
|
||||
height: WINDOW_HEIGHT + (process.platform == "win32" ? 40 : 0),
|
||||
frame: true,
|
||||
width: WINDOW_WIDTH,
|
||||
height: WINDOW_HEIGHT,
|
||||
frame: dev_tools,
|
||||
resizable: dev_tools,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
//frame: false,
|
||||
|
||||
webPreferences: {
|
||||
webSecurity: false,
|
||||
nodeIntegrationInWorker: true
|
||||
nodeIntegrationInWorker: false,
|
||||
nodeIntegration: true
|
||||
}
|
||||
});
|
||||
gui.setMenu(null);
|
||||
gui.loadFile(path.join(path.dirname(module.filename), "ui", "loading_screen.html"));
|
||||
gui.on('closed', close_callback);
|
||||
gui.on('closed', () => {
|
||||
if(resolve)
|
||||
resolve();
|
||||
gui = undefined;
|
||||
cleanup();
|
||||
});
|
||||
|
||||
gui.on('ready-to-show', () => {
|
||||
gui.show();
|
||||
gui.setPosition(x, y);
|
||||
winmgr.apply_bounds('ui-load-window', gui, undefined, { apply_size: false }).then(() => {
|
||||
winmgr.track_bounds('ui-load-window', gui);
|
||||
|
||||
const call_loader = () => load_files().catch(reject);
|
||||
if(!process_args.has_flag(...Arguments.DISABLE_ANIMATION))
|
||||
setTimeout(call_loader, 1000);
|
||||
else
|
||||
setImmediate(call_loader);
|
||||
const call_loader = () => load_files().catch(reject);
|
||||
if(!process_args.has_flag(...Arguments.DISABLE_ANIMATION))
|
||||
setTimeout(call_loader, 1000);
|
||||
else
|
||||
setImmediate(call_loader);
|
||||
|
||||
if(dev_tools)
|
||||
gui.webContents.openDevTools();
|
||||
if(dev_tools)
|
||||
gui.webContents.openDevTools();
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
@ -123,7 +135,7 @@ export namespace ui {
|
||||
console.error("Failed to load UI files! Error: %o", error)
|
||||
});
|
||||
|
||||
spawn_gui(() => reject(undefined));
|
||||
spawn_gui();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,27 @@
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
background: #18BC9C;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
body {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
|
||||
margin-left: 18px;
|
||||
margin-right: 18px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
-ms-overflow-style: none;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
img {
|
||||
@ -29,6 +46,7 @@
|
||||
}
|
||||
|
||||
.container-logo {
|
||||
align-self: center;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
|
@ -1,14 +1,32 @@
|
||||
import * as electron from "electron";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as winmgr from "../window";
|
||||
|
||||
export function open_preview(url: string) {
|
||||
export async function open_preview(url: string) {
|
||||
console.log("Open URL as preview: %s", url);
|
||||
const window = new electron.BrowserWindow();
|
||||
window.loadURL(url);
|
||||
const window = new electron.BrowserWindow({
|
||||
webPreferences: {
|
||||
webSecurity: true,
|
||||
nodeIntegration: false,
|
||||
nodeIntegrationInWorker: false,
|
||||
allowRunningInsecureContent: false,
|
||||
},
|
||||
skipTaskbar: true,
|
||||
center: true,
|
||||
});
|
||||
await winmgr.apply_bounds('url-preview', window);
|
||||
winmgr.track_bounds('url-preview', window);
|
||||
window.setMenu(null);
|
||||
|
||||
window.loadURL(url).then(() => {
|
||||
window.webContents.openDevTools();
|
||||
});
|
||||
|
||||
//FIXME try catch?
|
||||
const inject_file = path.join(path.dirname(module.filename), "inject.js");
|
||||
|
||||
const code_inject = fs.readFileSync(inject_file).toString();
|
||||
window.webContents.once('dom-ready', e => {
|
||||
const code_inject = fs.readFileSync(inject_file).toString();
|
||||
window.webContents.executeJavaScript(code_inject, true);
|
||||
|
109
modules/core/window.ts
Normal file
109
modules/core/window.ts
Normal file
@ -0,0 +1,109 @@
|
||||
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");
|
||||
|
||||
import BrowserWindow = Electron.BrowserWindow;
|
||||
import Rectangle = Electron.Rectangle;
|
||||
|
||||
let _changed_data: {[key: string]:Rectangle} = {};
|
||||
let _changed_saver: NodeJS.Timer;
|
||||
|
||||
export async function save_changes() {
|
||||
clearTimeout(_changed_saver);
|
||||
|
||||
try {
|
||||
const data = (await fs.pathExists(data_file) ? await fs.readJson(data_file) : {}) || {};
|
||||
Object.assign(data, _changed_data);
|
||||
|
||||
await fs.ensureFile(data_file);
|
||||
await fs.writeJson(data_file, data);
|
||||
path_exists = true;
|
||||
|
||||
_changed_data = {};
|
||||
} catch(error) {
|
||||
console.warn("Failed to save window bounds: %o", error);
|
||||
}
|
||||
console.log("Window bounds have been successfully saved!");
|
||||
}
|
||||
|
||||
let path_exists = undefined;
|
||||
export async function get_last_bounds(key: string) : Promise<Rectangle> {
|
||||
try {
|
||||
if(typeof(path_exists) === "undefined" ? !(path_exists = await fs.pathExists(data_file)) : !path_exists)
|
||||
throw "skip!";
|
||||
|
||||
const data = await fs.readJson(data_file) || {};
|
||||
if(data[key])
|
||||
return data[key];
|
||||
} catch(error) {
|
||||
if(error !== "skip!")
|
||||
console.warn("Failed to load window bounds for %s: %o", key, error);
|
||||
}
|
||||
|
||||
return {
|
||||
height: undefined,
|
||||
width: undefined,
|
||||
x: undefined,
|
||||
y: undefined
|
||||
}
|
||||
}
|
||||
|
||||
export function track_bounds(key: string, window: BrowserWindow) {
|
||||
const events = ['move', 'moved', 'resize'];
|
||||
|
||||
const update_bounds = () => {
|
||||
_changed_data[key] = window.getBounds();
|
||||
|
||||
clearTimeout(_changed_saver);
|
||||
_changed_saver = setTimeout(save_changes, 1000);
|
||||
};
|
||||
|
||||
for(const event of events)
|
||||
window.on(event as any, update_bounds);
|
||||
|
||||
window.on('closed', () => {
|
||||
for(const event of events)
|
||||
window.removeListener(event as any, update_bounds);
|
||||
})
|
||||
}
|
||||
|
||||
export async function apply_bounds(key: string, window: BrowserWindow, bounds?: Rectangle, options?: { apply_size?: boolean; apply_position?: boolean }) {
|
||||
const screen = electron.screen;
|
||||
|
||||
if(!bounds)
|
||||
bounds = await get_last_bounds(key);
|
||||
|
||||
if(!options)
|
||||
options = {};
|
||||
|
||||
const original_bounds = window.getBounds();
|
||||
|
||||
if(typeof(options.apply_size) !== "boolean" || options.apply_size) {
|
||||
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) {
|
||||
let x = typeof(bounds.x) === "number" ? bounds.x : original_bounds.x;
|
||||
let y = typeof(bounds.y) === "number" ? bounds.y : original_bounds.y;
|
||||
|
||||
if(x != original_bounds.x || y != original_bounds.y) {
|
||||
const display = screen.getDisplayNearestPoint({ x: x, y: y });
|
||||
if(display) {
|
||||
const bounds = display.workArea || display.bounds;
|
||||
let flag_invalid = false;
|
||||
flag_invalid = flag_invalid || bounds.x > x || (bounds.x + bounds.width) < x;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
181
modules/renderer/context-menu.ts
Normal file
181
modules/renderer/context-menu.ts
Normal file
@ -0,0 +1,181 @@
|
||||
window["require_setup"](module);
|
||||
|
||||
import * as electron from "electron";
|
||||
const remote = electron.remote;
|
||||
const {Menu, MenuItem} = remote;
|
||||
|
||||
import {isFunction} from "util";
|
||||
import NativeImage = electron.NativeImage;
|
||||
|
||||
class ElectronContextMenu implements contextmenu.ContextMenuProvider {
|
||||
private _close_listeners: (() => any)[] = [];
|
||||
private _current_menu: electron.Menu;
|
||||
|
||||
private _icon_mash_url: string;
|
||||
private _icon_mask_img: NativeImage;
|
||||
private _div: JQuery;
|
||||
|
||||
despawn_context_menu() {
|
||||
if(!this._current_menu)
|
||||
return;
|
||||
this._current_menu.closePopup();
|
||||
this._current_menu = undefined;
|
||||
|
||||
for(const listener of this._close_listeners)
|
||||
listener();
|
||||
this._close_listeners = [];
|
||||
}
|
||||
|
||||
finalize() {
|
||||
this._icon_mask_img = undefined;
|
||||
this._icon_mash_url = undefined;
|
||||
if(this._div) this._div.detach();
|
||||
this._div = undefined;
|
||||
this._cache_klass_map = undefined;
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.initialize_icons();
|
||||
}
|
||||
|
||||
private async initialize_icons() {
|
||||
if(!this._div) {
|
||||
this._div = $.spawn("div");
|
||||
this._div.css('display', 'none');
|
||||
this._div.appendTo(document.body);
|
||||
}
|
||||
|
||||
const image = new Image();
|
||||
image.src = 'img/client_icon_sprite.svg';
|
||||
await new Promise((resolve, reject) => {
|
||||
image.onload = resolve;
|
||||
image.onerror = reject;
|
||||
});
|
||||
|
||||
/* TODO: Get a size! */
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = 1024;
|
||||
canvas.height = 1024;
|
||||
canvas.getContext("2d").drawImage(image, 0, 0);
|
||||
|
||||
this._icon_mash_url = canvas.toDataURL();
|
||||
this._icon_mask_img = remote.nativeImage.createFromDataURL(this._icon_mash_url);
|
||||
}
|
||||
|
||||
|
||||
private _cache_klass_map: {[key: string]: NativeImage} = {};
|
||||
private class_to_image(klass: string) : NativeImage {
|
||||
if(!klass || !this._icon_mask_img)
|
||||
return undefined;
|
||||
if(this._cache_klass_map[klass])
|
||||
return this._cache_klass_map[klass];
|
||||
|
||||
this._div[0].classList.value = 'icon ' + klass;
|
||||
const data = window.getComputedStyle(this._div[0]);
|
||||
|
||||
const offset_x = parseInt(data.backgroundPositionX.split(",")[0]);
|
||||
const offset_y = parseInt(data.backgroundPositionY.split(",")[0]);
|
||||
|
||||
//http://localhost/home/TeaSpeak/Web-Client/web/environment/development/img/client_icon_sprite.svg
|
||||
//const hight = element.css('height');
|
||||
//const width = element.css('width');
|
||||
console.log("Offset: x: %o y: %o;", offset_x, offset_y);
|
||||
return this._cache_klass_map[klass] = this._icon_mask_img.crop({
|
||||
height: 16,
|
||||
width: 16,
|
||||
x: offset_x == 0 ? 0 : -offset_x,
|
||||
y: offset_y == 0 ? 0 : -offset_y
|
||||
});
|
||||
}
|
||||
|
||||
private _entry_id = 0;
|
||||
private build_menu(entry: contextmenu.MenuEntry) : electron.MenuItem {
|
||||
if(entry.type == contextmenu.MenuEntryType.CLOSE) {
|
||||
this._close_listeners.push(entry.callback);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const click_callback = () => {
|
||||
if(entry.callback)
|
||||
entry.callback();
|
||||
this.despawn_context_menu();
|
||||
};
|
||||
const _id = "entry_" + (this._entry_id++);
|
||||
if(entry.type == contextmenu.MenuEntryType.ENTRY) {
|
||||
return new MenuItem({
|
||||
id: _id,
|
||||
label: (isFunction(entry.name) ? (entry.name as (() => string))() : entry.name) as string,
|
||||
type: "normal",
|
||||
click: click_callback,
|
||||
icon: this.class_to_image(entry.icon_class),
|
||||
visible: entry.visible,
|
||||
enabled: !entry.disabled
|
||||
});
|
||||
} else if(entry.type == contextmenu.MenuEntryType.HR) {
|
||||
if(typeof(entry.visible) === "boolean" && !entry.visible)
|
||||
return undefined;
|
||||
|
||||
return new MenuItem({
|
||||
id: _id,
|
||||
type: "separator",
|
||||
label: '',
|
||||
click: click_callback
|
||||
})
|
||||
} else if(entry.type == contextmenu.MenuEntryType.CHECKBOX) {
|
||||
return new MenuItem({
|
||||
id: _id,
|
||||
label: (isFunction(entry.name) ? (entry.name as (() => string))() : entry.name) as string,
|
||||
type: "checkbox",
|
||||
checked: !!entry.checkbox_checked,
|
||||
click: click_callback,
|
||||
icon: this.class_to_image(entry.icon_class),
|
||||
visible: entry.visible,
|
||||
enabled: !entry.disabled
|
||||
});
|
||||
} else if (entry.type == contextmenu.MenuEntryType.SUB_MENU) {
|
||||
const sub_menu = new Menu();
|
||||
for(const e of entry.sub_menu) {
|
||||
const build = this.build_menu(e);
|
||||
if(!build)
|
||||
continue;
|
||||
sub_menu.append(build);
|
||||
}
|
||||
return new MenuItem({
|
||||
id: _id,
|
||||
label: (isFunction(entry.name) ? (entry.name as (() => string))() : entry.name) as string,
|
||||
type: "submenu",
|
||||
submenu: sub_menu,
|
||||
click: click_callback,
|
||||
icon: this.class_to_image(entry.icon_class),
|
||||
visible: entry.visible,
|
||||
enabled: !entry.disabled
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
spawn_context_menu(x: number, y: number, ...entries: contextmenu.MenuEntry[]) {
|
||||
this.despawn_context_menu();
|
||||
|
||||
this._current_menu = new Menu();
|
||||
for(const entry of entries) {
|
||||
const build = this.build_menu(entry);
|
||||
if(!build)
|
||||
continue;
|
||||
this._current_menu.append(build);
|
||||
}
|
||||
|
||||
this._current_menu.popup({
|
||||
window: remote.getCurrentWindow(),
|
||||
x: x,
|
||||
y: y,
|
||||
callback: () => this.despawn_context_menu()
|
||||
});
|
||||
}
|
||||
|
||||
html_format_enabled() { return false; }
|
||||
}
|
||||
|
||||
contextmenu.set_provider(new ElectronContextMenu());
|
||||
|
||||
export {};
|
@ -98,6 +98,26 @@ export const initialize = async () => {
|
||||
},
|
||||
priority: 110
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.INITIALIZING, {
|
||||
name: 'gdb-waiter',
|
||||
function: async () => {
|
||||
if(process_args.has_flag(Arguments.DEV_TOOLS_GDB)) {
|
||||
console.log("Process ID: %d", process.pid);
|
||||
await new Promise(resolve => {
|
||||
console.log("Waiting for continue!");
|
||||
|
||||
const listener = () => {
|
||||
console.log("Continue");
|
||||
document.removeEventListener('click', listener);
|
||||
resolve();
|
||||
};
|
||||
document.addEventListener('click', listener);
|
||||
});
|
||||
}
|
||||
},
|
||||
priority: 100
|
||||
});
|
||||
};
|
||||
|
||||
const jquery_initialize = async () => {
|
||||
@ -216,6 +236,13 @@ const load_modules = async () => {
|
||||
console.dir(error);
|
||||
throw error;
|
||||
}
|
||||
try {
|
||||
require("./context-menu");
|
||||
} catch(error) {
|
||||
console.error("Failed to load context menu extension");
|
||||
console.dir(error);
|
||||
throw error;
|
||||
}
|
||||
} catch(error){
|
||||
console.log(error);
|
||||
window.displayCriticalError("Failed to load native extensions: " + error);
|
||||
|
@ -3,6 +3,7 @@ import {app} from "electron";
|
||||
|
||||
export class Arguments {
|
||||
static readonly DEV_TOOLS = ["t", "dev-tools"];
|
||||
static readonly DEV_TOOLS_GDB = ["gdb"];
|
||||
static readonly DEBUG = ["d", "debug"];
|
||||
static readonly DISABLE_ANIMATION = ["a", "disable-animation"];
|
||||
static readonly SERVER_URL = ["u", "server-url"];
|
||||
|
@ -10,7 +10,7 @@ set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
function(setup_nodejs)
|
||||
set(NodeJS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
|
||||
set(NODEJS_URL "https://atom.io/download/atom-shell")
|
||||
set(NODEJS_VERSION "v4.0.5")
|
||||
set(NODEJS_VERSION "v5.0.6")
|
||||
|
||||
find_package(NodeJS REQUIRED)
|
||||
|
||||
|
@ -86,14 +86,14 @@ ssize_t AudioOutputSource::enqueue_samples(const std::shared_ptr<tc::audio::Samp
|
||||
this->sample_buffers.clear();
|
||||
break;
|
||||
case overflow_strategy::discard_buffer_half:
|
||||
this->sample_buffers.erase(this->sample_buffers.begin(), this->sample_buffers.begin() + this->sample_buffers.size() / 2);
|
||||
this->sample_buffers.erase(this->sample_buffers.begin(), this->sample_buffers.begin() + (int) (this->sample_buffers.size() / 2));
|
||||
break;
|
||||
case overflow_strategy::ignore:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this->sample_buffers.push_back(std::move(buf));
|
||||
this->sample_buffers.push_back(buf);
|
||||
this->buffered_samples += buf->sample_size;
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ AudioResampler::AudioResampler(size_t irate, size_t orate, size_t channels) : _i
|
||||
this->soxr_handle = soxr_create(this->_input_rate, this->_output_rate, (unsigned) this->_channels, &error, nullptr, nullptr, nullptr);
|
||||
|
||||
if(!this->soxr_handle) {
|
||||
log_error(category::audio, tr("Failed to create soxr resampler: {}"), error);
|
||||
log_error(category::audio, tr("Failed to create soxr resampler: {}. Input: {}; Output: {}; Channels: {}"), error, this->_input_rate, this->_output_rate, this->_channels);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,9 @@ namespace tc {
|
||||
virtual bool valid() = 0;
|
||||
virtual void finalize() = 0;
|
||||
|
||||
virtual void reset_encoder() = 0;
|
||||
virtual void reset_decoder() = 0;
|
||||
|
||||
/**
|
||||
* @return number of bytes written on success
|
||||
*/
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "OpusConverter.h"
|
||||
#include "../../logger.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace tc::audio::codec;
|
||||
@ -11,27 +12,57 @@ bool OpusConverter::valid() {
|
||||
}
|
||||
|
||||
bool OpusConverter::initialize(std::string &error, int application_type) {
|
||||
int error_id = 0;
|
||||
lock_guard lock(this->coder_lock);
|
||||
this->_application_type = application_type;
|
||||
|
||||
this->encoder = opus_encoder_create((opus_int32) this->_sample_rate, (int) this->_channels, application_type, &error_id);
|
||||
if(!this->encoder || error_id) {
|
||||
error = "failed to create encoder (" + to_string(error_id) + ")";
|
||||
if(!this->_initialize_encoder(error))
|
||||
return false;
|
||||
}
|
||||
|
||||
this->decoder = opus_decoder_create((opus_int32) this->_sample_rate, (int) this->_channels, &error_id);
|
||||
if(!this->encoder || error_id) {
|
||||
opus_encoder_destroy(this->encoder);
|
||||
this->encoder = nullptr;
|
||||
|
||||
error = "failed to create decoder (" + to_string(error_id) + ")";
|
||||
if(!this->_initialize_decoder(error)) {
|
||||
this->reset_encoder();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpusConverter::reset_encoder() {
|
||||
lock_guard lock(this->coder_lock);
|
||||
|
||||
string error;
|
||||
bool flag_error = false;
|
||||
|
||||
if(!(flag_error |= !this->_finalize_encoder(error))) {
|
||||
error = "finalize failed (" + error + ")";
|
||||
}
|
||||
|
||||
if(!flag_error && !(flag_error |= !this->_initialize_encoder(error))) {
|
||||
error = "initialize failed (" + error + ")";
|
||||
}
|
||||
|
||||
if(flag_error)
|
||||
log_warn(category::audio, tr("Failed to reset opus encoder: {}"), error);
|
||||
}
|
||||
|
||||
void OpusConverter::reset_decoder() {
|
||||
lock_guard lock(this->coder_lock);
|
||||
|
||||
string error;
|
||||
bool flag_error = false;
|
||||
|
||||
if(!(flag_error |= !this->_finalize_decoder(error))) {
|
||||
error = "finalize failed (" + error + ")";
|
||||
}
|
||||
|
||||
if(!flag_error && !(flag_error |= !this->_initialize_decoder(error))) {
|
||||
error = "initialize failed (" + error + ")";
|
||||
}
|
||||
|
||||
if(flag_error)
|
||||
log_warn(category::audio, tr("Failed to reset opus decoder: {}"), error);
|
||||
}
|
||||
|
||||
|
||||
void OpusConverter::finalize() {
|
||||
lock_guard lock(this->coder_lock);
|
||||
|
||||
@ -78,3 +109,45 @@ size_t OpusConverter::expected_encoded_length(size_t sample_count) {
|
||||
//TODO calculate stuff
|
||||
return 512;
|
||||
}
|
||||
|
||||
bool OpusConverter::_initialize_decoder(std::string &error) {
|
||||
if(!this->_finalize_decoder(error))
|
||||
return false;
|
||||
|
||||
int error_id = 0;
|
||||
this->decoder = opus_decoder_create((opus_int32) this->_sample_rate, (int) this->_channels, &error_id);
|
||||
if(!this->encoder || error_id) {
|
||||
error = "failed to create decoder (" + to_string(error_id) + ")";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpusConverter::_initialize_encoder(std::string &error) {
|
||||
if(!this->_finalize_encoder(error))
|
||||
return false;
|
||||
|
||||
int error_id = 0;
|
||||
this->encoder = opus_encoder_create((opus_int32) this->_sample_rate, (int) this->_channels, this->_application_type, &error_id);
|
||||
if(!this->encoder || error_id) {
|
||||
error = "failed to create encoder (" + to_string(error_id) + ")";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpusConverter::_finalize_decoder(std::string &) {
|
||||
if(this->decoder) {
|
||||
opus_decoder_destroy(this->decoder);
|
||||
this->decoder = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpusConverter::_finalize_encoder(std::string &) {
|
||||
if(this->encoder) {
|
||||
opus_encoder_destroy(this->encoder);
|
||||
this->encoder = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
@ -17,6 +17,9 @@ namespace tc {
|
||||
bool initialize(std::string& /* error */, int /* application type */);
|
||||
void finalize() override;
|
||||
|
||||
void reset_encoder() override;
|
||||
void reset_decoder() override;
|
||||
|
||||
ssize_t encode(std::string & /* error */, const void * /* source */, void * /* target */, size_t /* target size */) override;
|
||||
ssize_t decode(std::string & /* error */, const void */* source */, size_t /* source size */, void *pVoid1) override;
|
||||
|
||||
@ -27,6 +30,13 @@ namespace tc {
|
||||
std::mutex coder_lock;
|
||||
OpusDecoder* decoder = nullptr;
|
||||
OpusEncoder* encoder = nullptr;
|
||||
|
||||
int _application_type = 0;
|
||||
|
||||
bool _finalize_encoder(std::string& /* error */);
|
||||
bool _finalize_decoder(std::string& /* error */);
|
||||
bool _initialize_encoder(std::string& /* error */);
|
||||
bool _initialize_decoder(std::string& /* error */);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -418,8 +418,6 @@ NAN_METHOD(ServerConnection::send_command) {
|
||||
}
|
||||
this->protocol_handler->send_command(cmd);
|
||||
auto end = chrono::system_clock::now();
|
||||
log_trace(category::general, "Needed {}ms for command building & sending", chrono::duration_cast<chrono::milliseconds>(end - begin).count());
|
||||
log_trace(category::general, "Command: {}", cmd.build());
|
||||
}
|
||||
NAN_METHOD(ServerConnection::_send_voice_data) {
|
||||
return ObjectWrap::Unwrap<ServerConnection>(info.Holder())->send_voice_data(info);
|
||||
|
@ -17,11 +17,14 @@ VoiceSender::~VoiceSender() {
|
||||
this->clear_buffer(); /* buffer might be accessed within encode_raw_frame, but this could not be trigered while this will be deallocated! */
|
||||
}
|
||||
|
||||
bool VoiceSender::initialize_codec(std::string& error, connection::codec::value codec, size_t channels, size_t rate) {
|
||||
bool VoiceSender::initialize_codec(std::string& error, connection::codec::value codec, size_t channels, size_t rate, bool reset_decoder) {
|
||||
auto& data = this->codec[codec];
|
||||
bool new_allocated = !data;
|
||||
if(new_allocated) data = make_unique<AudioCodec>();
|
||||
data->successfully_initialized = false;
|
||||
if(new_allocated) {
|
||||
data = make_unique<AudioCodec>();
|
||||
data->packet_counter = 0;
|
||||
data->last_packet = chrono::system_clock::now();
|
||||
}
|
||||
|
||||
auto info = codec::get_info(codec);
|
||||
if(!info || !info->supported) {
|
||||
@ -34,7 +37,10 @@ bool VoiceSender::initialize_codec(std::string& error, connection::codec::value
|
||||
data->converter = info->new_converter(error);
|
||||
if(!data->converter)
|
||||
return false;
|
||||
} else if(reset_decoder) {
|
||||
data->converter->reset_encoder();
|
||||
}
|
||||
|
||||
if(!data->resampler || data->resampler->input_rate() != rate)
|
||||
data->resampler = make_shared<AudioResampler>(rate, data->converter->sample_rate(), data->converter->channels());
|
||||
if(!data->resampler->valid()) {
|
||||
@ -42,7 +48,6 @@ bool VoiceSender::initialize_codec(std::string& error, connection::codec::value
|
||||
return false;
|
||||
}
|
||||
|
||||
data->successfully_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -52,6 +57,8 @@ void VoiceSender::send_data(const void *data, size_t samples, size_t rate, size_
|
||||
log_warn(category::voice_connection, tr("Dropping raw audio frame because of an invalid handle."));
|
||||
return;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
|
||||
auto frame = make_unique<AudioFrame>();
|
||||
frame->sample_rate = rate;
|
||||
@ -59,23 +66,35 @@ void VoiceSender::send_data(const void *data, size_t samples, size_t rate, size_
|
||||
frame->buffer = pipes::buffer{(void*) data, samples * channels * 4};
|
||||
frame->timestamp = chrono::system_clock::now();
|
||||
|
||||
/*
|
||||
{
|
||||
lock_guard buffer_lock(this->raw_audio_buffer_lock);
|
||||
this->raw_audio_buffers.push_back(move(frame));
|
||||
}
|
||||
|
||||
audio::encode_event_loop->schedule(static_pointer_cast<event::EventEntry>(this->_ref.lock()));
|
||||
*/
|
||||
lock.unlock();
|
||||
encode_raw_frame(frame);
|
||||
}
|
||||
|
||||
void VoiceSender::send_stop() {
|
||||
lock_guard lock(this->_execute_lock);
|
||||
unique_lock lock(this->_execute_lock);
|
||||
if(!this->handle) {
|
||||
log_warn(category::voice_connection, tr("Dropping audio end frame because of an invalid handle."));
|
||||
return;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
auto server = this->handle->handle();
|
||||
server->send_voice_data(nullptr, 0, this->_current_codec, false);
|
||||
|
||||
auto frame = make_unique<AudioFrame>();
|
||||
frame->sample_rate = 0;
|
||||
frame->channels = 0;
|
||||
frame->buffer = pipes::buffer{nullptr, 0};
|
||||
frame->timestamp = chrono::system_clock::now();
|
||||
|
||||
{
|
||||
lock_guard buffer_lock(this->raw_audio_buffer_lock);
|
||||
this->raw_audio_buffers.push_back(move(frame));
|
||||
}
|
||||
|
||||
audio::encode_event_loop->schedule(static_pointer_cast<event::EventEntry>(this->_ref.lock()));
|
||||
}
|
||||
|
||||
void VoiceSender::finalize() {
|
||||
@ -114,15 +133,42 @@ void VoiceSender::encode_raw_frame(const std::unique_ptr<AudioFrame> &frame) {
|
||||
auto codec = this->_current_codec;
|
||||
auto& codec_data = this->codec[codec];
|
||||
|
||||
string error;
|
||||
bool flag_head = true, flag_reset = true;
|
||||
if(codec_data) {
|
||||
if(codec_data->last_packet + chrono::seconds(1) < frame->timestamp)
|
||||
codec_data->packet_counter = 0;
|
||||
|
||||
if(!this->initialize_codec(error, codec, frame->channels, frame->sample_rate)) {
|
||||
flag_head = codec_data->packet_counter < 5;
|
||||
flag_reset = codec_data->packet_counter == 0;
|
||||
|
||||
codec_data->packet_counter++;
|
||||
codec_data->last_packet = frame->timestamp;
|
||||
}
|
||||
|
||||
if(frame->channels == 0 || frame->sample_rate == 0 || frame->buffer.empty()) {
|
||||
lock_guard lock(this->_execute_lock);
|
||||
if(!this->handle) {
|
||||
log_warn(category::voice_connection, tr("Dropping audio end because of an invalid handle."));
|
||||
return;
|
||||
}
|
||||
|
||||
if(codec_data)
|
||||
codec_data->packet_counter = 0;
|
||||
auto server = this->handle->handle();
|
||||
server->send_voice_data(this->_buffer, 0, codec, flag_head);
|
||||
return;
|
||||
}
|
||||
|
||||
string error;
|
||||
if(flag_reset) {
|
||||
log_trace(category::voice_connection, tr("Resetting encoder for voice sender"));
|
||||
}
|
||||
if(!this->initialize_codec(error, codec, frame->channels, frame->sample_rate, flag_reset)) {
|
||||
log_error(category::voice_connection, tr("Failed to initialize codec: {}"), error);
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: May test for channel and sample rate? */
|
||||
|
||||
this->ensure_buffer(codec_data->resampler->estimated_output_size(frame->buffer.length()));
|
||||
auto resampled_samples = codec_data->resampler->process(this->_buffer, frame->buffer.data_ptr(), frame->buffer.length() / frame->channels / 4);
|
||||
if(resampled_samples <= 0) {
|
||||
@ -144,6 +190,6 @@ void VoiceSender::encode_raw_frame(const std::unique_ptr<AudioFrame> &frame) {
|
||||
}
|
||||
|
||||
auto server = this->handle->handle();
|
||||
server->send_voice_data(this->_buffer, encoded_bytes, codec, false);
|
||||
server->send_voice_data(this->_buffer, encoded_bytes, codec, flag_head);
|
||||
}
|
||||
}
|
||||
|
@ -36,13 +36,15 @@ namespace tc {
|
||||
VoiceConnection* handle;
|
||||
|
||||
struct AudioCodec {
|
||||
bool successfully_initialized;
|
||||
size_t packet_counter = 0;
|
||||
std::chrono::system_clock::time_point last_packet;
|
||||
|
||||
std::shared_ptr<audio::codec::Converter> converter;
|
||||
std::shared_ptr<audio::AudioResampler> resampler;
|
||||
};
|
||||
std::array<std::unique_ptr<AudioCodec>, codec::MAX + 1> codec{nullptr};
|
||||
|
||||
bool initialize_codec(std::string&, codec::value /* codec */, size_t /* channels */, size_t /* source sample rate */);
|
||||
bool initialize_codec(std::string&, codec::value /* codec */, size_t /* channels */, size_t /* source sample rate */, bool /* reset decoder */);
|
||||
|
||||
codec::value _current_codec = codec::OPUS_VOICE;
|
||||
|
||||
|
@ -246,7 +246,7 @@ void VoiceClient::finalize_js_object() {
|
||||
}
|
||||
|
||||
#define target_buffer_length 16384
|
||||
void VoiceClient::process_packet(uint16_t packet_id, const pipes::buffer_view& buffer, codec::value codec) {
|
||||
void VoiceClient::process_packet(uint16_t packet_id, const pipes::buffer_view& buffer, codec::value codec, bool head) {
|
||||
if(this->_volume == 0)
|
||||
return;
|
||||
|
||||
@ -255,17 +255,12 @@ void VoiceClient::process_packet(uint16_t packet_id, const pipes::buffer_view& b
|
||||
return;
|
||||
}
|
||||
|
||||
if(buffer.empty()) {
|
||||
log_trace(category::voice_connection, tr("Stopping replay for client {}. Empty buffer!"), this->_client_id);
|
||||
this->set_state(state::stopping);
|
||||
return;
|
||||
}
|
||||
|
||||
auto encoded_buffer = make_unique<EncodedBuffer>();
|
||||
encoded_buffer->packet_id = packet_id;
|
||||
encoded_buffer->codec = codec;
|
||||
encoded_buffer->receive_timestamp = chrono::system_clock::now();
|
||||
encoded_buffer->buffer = buffer.own_buffer();
|
||||
encoded_buffer->head = head;
|
||||
|
||||
{
|
||||
lock_guard lock(this->audio_decode_queue_lock);
|
||||
@ -315,13 +310,13 @@ void VoiceClient::process_encoded_buffer(const std::unique_ptr<EncodedBuffer> &b
|
||||
|
||||
auto& codec_data = this->codec[buffer->codec];
|
||||
|
||||
auto info = codec::get_info(buffer->codec);
|
||||
if(!info || !info->supported) {
|
||||
log_warn(category::voice_connection, tr("Received voice packet from client {}, but we dont support it ({})"), this->_client_id, buffer->codec);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!codec_data) {
|
||||
auto info = codec::get_info(buffer->codec);
|
||||
if(!info || !info->supported) {
|
||||
log_warn(category::voice_connection, tr("Received voice packet from client {}, but we dont support it ({})"), this->_client_id, buffer->codec);
|
||||
return;
|
||||
}
|
||||
|
||||
auto instance = make_unique<AudioCodec>();
|
||||
instance->successfully_initialized = false;
|
||||
|
||||
@ -357,6 +352,21 @@ void VoiceClient::process_encoded_buffer(const std::unique_ptr<EncodedBuffer> &b
|
||||
} else {
|
||||
diff = buffer->packet_id - codec_data->last_packet_id;
|
||||
}
|
||||
|
||||
if(codec_data->last_packet_timestamp + chrono::seconds(1) < buffer->receive_timestamp)
|
||||
diff = 0xFFFF;
|
||||
|
||||
const auto old_packet_id = codec_data->last_packet_id;
|
||||
codec_data->last_packet_timestamp = buffer->receive_timestamp;
|
||||
codec_data->last_packet_id = buffer->packet_id;
|
||||
|
||||
if(buffer->buffer.empty()) {
|
||||
/* lets playpack the last samples and we're done */
|
||||
this->set_state(state::stopping);
|
||||
log_trace(category::voice_connection, tr("Stopping replay for client {}. Empty buffer!"), this->_client_id);
|
||||
return;
|
||||
}
|
||||
|
||||
if(diff == 0) {
|
||||
//Duplicated packets
|
||||
log_warn(category::audio, tr("Received voice packet with the same ID then the last one. Dropping packet."));
|
||||
@ -366,20 +376,19 @@ void VoiceClient::process_encoded_buffer(const std::unique_ptr<EncodedBuffer> &b
|
||||
|
||||
if(diff <= MAX_LOST_PACKETS) {
|
||||
if(diff > 0) {
|
||||
log_debug(category::voice_connection, tr("Client {} dropped one or more audio packets. Old packet id: {}, New packet id: {}, Diff: {}"), this->_client_id, codec_data->last_packet_id, buffer->packet_id, diff);
|
||||
log_debug(category::voice_connection, tr("Client {} dropped one or more audio packets. Old packet id: {}, New packet id: {}, Diff: {}"), this->_client_id, old_packet_id, buffer->packet_id, diff);
|
||||
auto status = codec_data->converter->decode_lost(error, diff);
|
||||
if(status < 0)
|
||||
log_warn(category::voice_connection, tr("Failed to decode (skip) dropped packets. Return code {} => {}"), status, error);
|
||||
}
|
||||
} else {
|
||||
log_debug(category::voice_connection, tr("Client {} reinitialized decoder. Old packet id: {}, New packet id: {}"), this->_client_id, codec_data->last_packet_id, buffer->packet_id);
|
||||
codec_data->converter = info->new_converter(error);
|
||||
log_debug(category::voice_connection, tr("Client {} resetted decoder. Old packet id: {}, New packet id: {}, diff: {}"), this->_client_id, old_packet_id, buffer->packet_id, diff);
|
||||
codec_data->converter->reset_decoder();
|
||||
if(!codec_data->converter) {
|
||||
log_warn(category::voice_connection, tr("Failed to initialize new codec decoder {} for client {}: {}"), buffer->codec, this->_client_id, error);
|
||||
log_warn(category::voice_connection, tr("Failed to reset codec decoder {} for client {}: {}"), buffer->codec, this->_client_id, error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
codec_data->last_packet_id = buffer->packet_id;
|
||||
|
||||
char target_buffer[target_buffer_length];
|
||||
if(target_buffer_length < codec_data->converter->expected_decoded_length(buffer->buffer.data_ptr(), buffer->buffer.length())) {
|
||||
|
@ -75,7 +75,7 @@ namespace tc {
|
||||
|
||||
inline std::shared_ptr<VoiceClient> ref() { return this->_ref.lock(); }
|
||||
|
||||
void process_packet(uint16_t packet_id, const pipes::buffer_view& /* buffer */, codec::value /* codec */);
|
||||
void process_packet(uint16_t packet_id, const pipes::buffer_view& /* buffer */, codec::value /* codec */, bool /* head */);
|
||||
|
||||
inline float get_volume() { return this->_volume; }
|
||||
inline void set_volume(float value) { this->_volume = value; }
|
||||
@ -90,6 +90,8 @@ namespace tc {
|
||||
private:
|
||||
struct AudioCodec {
|
||||
uint16_t last_packet_id = 0;
|
||||
std::chrono::system_clock::time_point last_packet_timestamp;
|
||||
|
||||
bool successfully_initialized;
|
||||
std::shared_ptr<audio::codec::Converter> converter;
|
||||
std::shared_ptr<audio::AudioResampler> resampler;
|
||||
@ -120,6 +122,7 @@ namespace tc {
|
||||
}
|
||||
|
||||
struct EncodedBuffer {
|
||||
bool head;
|
||||
uint16_t packet_id;
|
||||
pipes::buffer buffer;
|
||||
codec::value codec;
|
||||
|
@ -296,7 +296,7 @@ void VoiceConnection::process_packet(const std::shared_ptr<ts::protocol::ServerP
|
||||
auto packet_id = be2le16(&packet->data()[0]);
|
||||
auto client_id = be2le16(&packet->data()[2]);
|
||||
auto codec_id = (uint8_t) packet->data()[4];
|
||||
//container->flag_head = packet->hasFlag(PacketFlag::Compressed);
|
||||
auto flag_head = packet->hasFlag(PacketFlag::Compressed);
|
||||
//container->voice_data = packet->data().length() > 5 ? packet->data().range(5) : pipes::buffer{};
|
||||
|
||||
auto client = this->find_client(client_id);
|
||||
@ -306,9 +306,9 @@ void VoiceConnection::process_packet(const std::shared_ptr<ts::protocol::ServerP
|
||||
}
|
||||
|
||||
if(packet->data().length() > 5)
|
||||
client->process_packet(packet_id, packet->data().range(5), (codec::value) codec_id);
|
||||
client->process_packet(packet_id, packet->data().range(5), (codec::value) codec_id, flag_head);
|
||||
else
|
||||
client->process_packet(packet_id, pipes::buffer_view{nullptr, 0}, (codec::value) codec_id);
|
||||
client->process_packet(packet_id, pipes::buffer_view{nullptr, 0}, (codec::value) codec_id, flag_head);
|
||||
} else {
|
||||
//TODO implement whisper
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/// <reference path="../../exports/exports.d.ts" />
|
||||
|
||||
module.paths.push("../../build/linux_amd64");
|
||||
module.paths.push("../../build/linux_x64");
|
||||
|
||||
import * as fs from "fs";
|
||||
import * as handle from "teaclient_connection";
|
||||
@ -115,74 +115,15 @@ class Bot {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
const bot_list = [];
|
||||
for(let index = 0; index < 20; index++) {
|
||||
const bot = new Bot();
|
||||
bot_list.push(bot);
|
||||
bot.connect();
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
const run = async () => {
|
||||
const connection = handle.spawn_server_connection();
|
||||
connection.connect({
|
||||
timeout: 5000,
|
||||
remote_port: port,
|
||||
remote_host: host,
|
||||
callback: error => {
|
||||
if(error == 0) {
|
||||
connection.send_command("clientinit", [
|
||||
{
|
||||
"client_key_offset": 2030434,
|
||||
//"client_version": "1.0.0",
|
||||
//"client_platform": "nodejs/linux",
|
||||
"client_version": "3.1.8 [Build: 1516614607]",
|
||||
"client_platform": "Windows",
|
||||
"client_version_sign": "gDEgQf/BiOQZdAheKccM1XWcMUj2OUQqt75oFuvF2c0MQMXyv88cZQdUuckKbcBRp7RpmLInto4PIgd7mPO7BQ==",
|
||||
|
||||
"client_nickname": "TeaClient Native Module Test",
|
||||
|
||||
"client_input_hardware":true,
|
||||
"client_output_hardware":true,
|
||||
"client_default_channel":"",
|
||||
"client_default_channel_password":"",
|
||||
"client_server_password":"",
|
||||
"client_meta_data":"",
|
||||
"client_nickname_phonetic":"",
|
||||
"client_default_token":"",
|
||||
"hwid":"123,456123123123",
|
||||
return_code:91
|
||||
}
|
||||
], []);
|
||||
} else {
|
||||
console.log("Bot connect failed: %o (%s) ", error, connection.error_message(error));
|
||||
}
|
||||
},
|
||||
|
||||
identity_key: "MG4DAgeAAgEgAiBC9JsqB1am6vowj2obomMyxm1GLk8qyRoxpBkAdiVYxwIgWksaSk7eyVQovZwPZBuiYHARz/xQD5zBUBK6e63V7hICIQCZ2glHe3kV62iIRKpkV2lzZGZtfBPRMbwIcU9aE1EVsg==",
|
||||
teamspeak: true
|
||||
});
|
||||
await new Promise(resolve => {
|
||||
connection.callback_command = command => {
|
||||
console.log("Having: %s", command);
|
||||
if(command === "channellistfinished") {
|
||||
connection.disconnect("XXXX", () => {
|
||||
console.log("disconnected!");
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
};
|
||||
setTimeout(resolve, 5000);
|
||||
});
|
||||
};
|
||||
(async () => {
|
||||
while(true) {
|
||||
await run();
|
||||
}
|
||||
})();
|
||||
*/
|
||||
|
||||
import * as net from "net";
|
||||
import * as tls from "tls";
|
||||
import * as https from "https";
|
||||
@ -202,3 +143,4 @@ const run = async () => {
|
||||
}
|
||||
};
|
||||
setInterval(run, 10);
|
||||
*/
|
42
package.json
42
package.json
@ -20,18 +20,18 @@
|
||||
"author": "TeaSpeak (WolverinDEV)",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/ejs": "^2.6.0",
|
||||
"@types/electron-packager": "^12.0.0",
|
||||
"@types/fs-extra": "^5.0.4",
|
||||
"@types/jquery": "^3.3.11",
|
||||
"@types/request": "^2.47.1",
|
||||
"@types/request-promise": "^4.1.42",
|
||||
"@types/tar-stream": "^1.6.0",
|
||||
"@types/ejs": "^2.6.3",
|
||||
"@types/electron-packager": "^14.0.0",
|
||||
"@types/fs-extra": "^8.0.0",
|
||||
"@types/jquery": "^3.3.30",
|
||||
"@types/request": "^2.48.1",
|
||||
"@types/request-promise": "^4.1.44",
|
||||
"@types/tar-stream": "^1.6.1",
|
||||
"asar": "^2.0.1",
|
||||
"cmake-js": "^4.0.1",
|
||||
"ejs": "^2.6.1",
|
||||
"nodemon": "^1.18.6",
|
||||
"electron-packager": "^12.2.0"
|
||||
"ejs": "^2.6.2",
|
||||
"electron-packager": "^14.0.0",
|
||||
"nodemon": "^1.19.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/minimist": "^1.2.0",
|
||||
@ -41,29 +41,29 @@
|
||||
"assert-plus": "^1.0.0",
|
||||
"aws-sign2": "^0.7.0",
|
||||
"aws4": "^1.8.0",
|
||||
"electron": "4.0.5",
|
||||
"electron-rebuild": "^1.8.2",
|
||||
"electron": "^5.0.6",
|
||||
"electron-rebuild": "^1.8.5",
|
||||
"extend": "^3.0.2",
|
||||
"extsprintf": "^1.4.0",
|
||||
"fs-extra": "^7.0.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"http-signature": "^1.2.0",
|
||||
"jquery": "^3.3.1",
|
||||
"jquery": "^3.4.1",
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
"jsprim": "^2.0.0",
|
||||
"jsrender": "^0.9.91",
|
||||
"jsrender": "^1.0.3",
|
||||
"nan": "^2.14.0",
|
||||
"node-ssh": "^6.0.0",
|
||||
"only": "0.0.2",
|
||||
"psl": "^1.1.29",
|
||||
"pure-uuid": "^1.5.4",
|
||||
"psl": "^1.1.33",
|
||||
"pure-uuid": "^1.5.7",
|
||||
"request": "^2.47.1",
|
||||
"request-progress": "^3.0.0",
|
||||
"request-promise": "^4.2.2",
|
||||
"request-promise": "^4.2.4",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"safer-buffer": "^2.1.2",
|
||||
"sshpk": "^1.14.2",
|
||||
"tar-stream": "^1.6.2",
|
||||
"tough-cookie": "^2.4.3"
|
||||
"sshpk": "^1.16.1",
|
||||
"tar-stream": "^2.1.0",
|
||||
"tough-cookie": "^3.0.1"
|
||||
},
|
||||
"overrides": {
|
||||
"os": {
|
||||
|
Loading…
Reference in New Issue
Block a user