Finally got fully rid of the initial backend glue and changes all systems to provider
parent
38ea11725f
commit
e2be859266
|
@ -1,4 +1,7 @@
|
||||||
# Changelog:
|
# Changelog:
|
||||||
|
* **18.03.21**
|
||||||
|
- Finally got fully rid of the initial backend glue and changes all systems to provider
|
||||||
|
|
||||||
* **17.03.21**
|
* **17.03.21**
|
||||||
- Updated from webpack 4 to webpack 5
|
- Updated from webpack 4 to webpack 5
|
||||||
- Reworked the client build process
|
- Reworked the client build process
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
# File structure
|
|
||||||
The TeaSpeak web client is separated into 2 different parts.
|
|
||||||
|
|
||||||
## I) Application files
|
|
||||||
Application files are all files which directly belong to the app itself.
|
|
||||||
Like the javascript files who handle the UI stuff or even translation templates.
|
|
||||||
Theses files are separated into two type of files.
|
|
||||||
1. [Shared application files](#1-shared-application-files)
|
|
||||||
2. [Web application files](#2-web-application-files)
|
|
||||||
|
|
||||||
### 1. Shared application files
|
|
||||||
Containing all files used by the TeaSpeak client and the Web client.
|
|
||||||
All of these files will be found within the folder `shared`.
|
|
||||||
This folder follows the general application file structure.
|
|
||||||
More information could be found [here](#application-file-structure)
|
|
||||||
|
|
||||||
### 2. Web application files
|
|
||||||
All files which only belong to a browser only instance.
|
|
||||||
All of these files will be found within the folder `web`.
|
|
||||||
This folder follows the general application file structure.
|
|
||||||
More information could be found [here](#application-file-structure)
|
|
||||||
|
|
||||||
### application file structure
|
|
||||||
Every application root contains several subfolders.
|
|
||||||
In the following list will be listed which files belong to which folder
|
|
||||||
|
|
||||||
| Folder | Description |
|
|
||||||
| --- | --- |
|
|
||||||
| `audio` | This folder contains all audio files used by the application. More information could be found [here](). |
|
|
||||||
| `css` | This folder contains all style sheets used by the application. More information could be found [here](). |
|
|
||||||
| `js` | This folder contains all javascript files used by the application. More information could be found [here](). |
|
|
||||||
| `html` | This folder contains all HTML and PHP files used by the application. More information could be found [here](). |
|
|
||||||
| `i18n` | This folder contains all default translations. Information about the translation system could be found [here](). |
|
|
||||||
| `img` | This folder contains all image files. |
|
|
||||||
|
|
||||||
## I) Additional tools
|
|
||||||
|
|
||||||
## Environment builder
|
|
||||||
The environment builder is one of the most important tools of the entire project.
|
|
||||||
This tool, basically implemented in the file `files.php`, will be your helper while live developing.
|
|
||||||
What this tool does is, it creates a final environment where you could navigate to with your browser.
|
|
||||||
It merges all the type separated files, which had been listed above ([here](#application-file-structure)).
|
|
|
@ -1,17 +0,0 @@
|
||||||
import {Device} from "tc-shared/audio/player";
|
|
||||||
|
|
||||||
export function initialize() : boolean;
|
|
||||||
export function initialized() : boolean;
|
|
||||||
|
|
||||||
export function get_master_volume() : number;
|
|
||||||
export function set_master_volume(volume: number);
|
|
||||||
|
|
||||||
export function on_ready(cb: () => any);
|
|
||||||
|
|
||||||
export function available_devices() : Promise<Device[]>;
|
|
||||||
export function set_device(device_id: string) : Promise<void>;
|
|
||||||
export function current_device() : Device;
|
|
||||||
|
|
||||||
export function initializeFromGesture();
|
|
||||||
|
|
||||||
export function globalAudioContext() : AudioContext;
|
|
|
@ -1,3 +0,0 @@
|
||||||
import {SoundFile} from "tc-shared/sound/Sounds";
|
|
||||||
|
|
||||||
export function play_sound(file: SoundFile) : Promise<void>;
|
|
|
@ -1,12 +0,0 @@
|
||||||
import {KeyEvent, KeyHook, SpecialKey} from "tc-shared/PPTListener";
|
|
||||||
|
|
||||||
export function initialize() : Promise<void>;
|
|
||||||
export function finalize(); // most the times not really required
|
|
||||||
|
|
||||||
export function register_key_listener(listener: (_: KeyEvent) => any);
|
|
||||||
export function unregister_key_listener(listener: (_: KeyEvent) => any);
|
|
||||||
|
|
||||||
export function register_key_hook(hook: KeyHook);
|
|
||||||
export function unregister_key_hook(hook: KeyHook);
|
|
||||||
|
|
||||||
export function key_pressed(code: string | SpecialKey) : boolean;
|
|
|
@ -1,2 +0,0 @@
|
||||||
This folder contains declarations files which are required to be implemented
|
|
||||||
Else the UI shared pack wound work
|
|
|
@ -2,7 +2,7 @@ import {AbstractServerConnection} from "./connection/ConnectionBase";
|
||||||
import {PermissionManager} from "./permission/PermissionManager";
|
import {PermissionManager} from "./permission/PermissionManager";
|
||||||
import {GroupManager} from "./permission/GroupManager";
|
import {GroupManager} from "./permission/GroupManager";
|
||||||
import {ServerSettings, Settings, settings, StaticSettings} from "./settings";
|
import {ServerSettings, Settings, settings, StaticSettings} from "./settings";
|
||||||
import {Sound, SoundManager} from "./sound/Sounds";
|
import {Sound, SoundManager} from "./audio/Sounds";
|
||||||
import {ConnectionProfile} from "./profiles/ConnectionProfile";
|
import {ConnectionProfile} from "./profiles/ConnectionProfile";
|
||||||
import {LogCategory, logError, logInfo, logTrace, logWarn} from "./log";
|
import {LogCategory, logError, logInfo, logTrace, logWarn} from "./log";
|
||||||
import {createErrorModal, createInfoModal, createInputModal, Modal} from "./ui/elements/Modal";
|
import {createErrorModal, createInfoModal, createInputModal, Modal} from "./ui/elements/Modal";
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import * as ppt from "tc-backend/ppt";
|
|
||||||
import * as log from "./log";
|
|
||||||
import {LogCategory, logError, logWarn} from "./log";
|
import {LogCategory, logError, logWarn} from "./log";
|
||||||
import {KeyDescriptor, KeyHook} from "./PPTListener";
|
import {getKeyBoard, KeyDescriptor, KeyHook} from "./PPTListener";
|
||||||
import {Settings, settings} from "./settings";
|
import {Settings, settings} from "./settings";
|
||||||
import {server_connections} from "tc-shared/ConnectionManager";
|
import {server_connections} from "tc-shared/ConnectionManager";
|
||||||
import {tr} from "./i18n/localize";
|
import {tr} from "./i18n/localize";
|
||||||
|
@ -181,18 +179,18 @@ function bindKey(action: string, key: KeyDescriptor) {
|
||||||
|
|
||||||
keyBindings[action] = {
|
keyBindings[action] = {
|
||||||
hook: Object.assign({
|
hook: Object.assign({
|
||||||
callback_press: () => control.handler(),
|
callbackPress: () => control.handler(),
|
||||||
callback_release: () => {},
|
callbackRelease: () => {},
|
||||||
cancel: false
|
cancel: false
|
||||||
}, key),
|
}, key),
|
||||||
binding: key
|
binding: key
|
||||||
};
|
};
|
||||||
ppt.register_key_hook(keyBindings[action].hook);
|
getKeyBoard().registerHook(keyBindings[action].hook);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setKey(action: string, key?: KeyDescriptor) {
|
export function setKey(action: string, key?: KeyDescriptor) {
|
||||||
if(typeof keyBindings[action] !== "undefined") {
|
if(typeof keyBindings[action] !== "undefined") {
|
||||||
ppt.unregister_key_hook(keyBindings[action].hook);
|
getKeyBoard().unregisterHook(keyBindings[action].hook);
|
||||||
delete keyBindings[action];
|
delete keyBindings[action];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { tr } from "./i18n/localize";
|
import { tr } from "./i18n/localize";
|
||||||
|
import {LogCategory, logTrace} from "tc-shared/log";
|
||||||
|
|
||||||
export enum KeyCode {
|
export enum KeyCode {
|
||||||
KEY_CANCEL = 3,
|
KEY_CANCEL = 3,
|
||||||
|
@ -134,57 +135,199 @@ export enum SpecialKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeyDescriptor {
|
export interface KeyDescriptor {
|
||||||
key_code: string;
|
keyCode: string;
|
||||||
|
|
||||||
key_ctrl: boolean;
|
keyCtrl: boolean;
|
||||||
key_windows: boolean;
|
keyWindows: boolean;
|
||||||
key_shift: boolean;
|
keyShift: boolean;
|
||||||
key_alt: boolean;
|
keyAlt: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeyEvent extends KeyDescriptor {
|
export interface KeyEvent extends KeyDescriptor {
|
||||||
readonly type: EventType;
|
readonly type: EventType;
|
||||||
|
|
||||||
readonly key: string;
|
readonly key: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeyHook extends KeyDescriptor {
|
export interface KeyHook extends KeyDescriptor {
|
||||||
cancel: boolean;
|
callbackPress: () => any;
|
||||||
|
callbackRelease: () => any;
|
||||||
callback_press: () => any;
|
|
||||||
callback_release: () => any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function key_description(key: KeyDescriptor) {
|
export interface KeyBoardBackend {
|
||||||
|
registerListener(listener: (event: KeyEvent) => void);
|
||||||
|
unregisterListener(listener: (event: KeyEvent) => void);
|
||||||
|
|
||||||
|
registerHook(hook: KeyHook);
|
||||||
|
unregisterHook(hook: KeyHook);
|
||||||
|
|
||||||
|
isKeyPressed(key: string | SpecialKey) : boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AbstractKeyBoard implements KeyBoardBackend {
|
||||||
|
protected readonly registeredListener: ((event: KeyEvent) => void)[];
|
||||||
|
protected readonly activeSpecialKeys: { [key: number] : boolean };
|
||||||
|
protected readonly activeKeys;
|
||||||
|
|
||||||
|
protected registeredKeyHooks: KeyHook[] = [];
|
||||||
|
protected activeKeyHooks: KeyHook[] = [];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.activeSpecialKeys = {};
|
||||||
|
this.activeKeys = {};
|
||||||
|
this.registeredListener = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected destroy() {}
|
||||||
|
|
||||||
|
isKeyPressed(key: string | SpecialKey): boolean {
|
||||||
|
if(typeof(key) === 'string') {
|
||||||
|
return typeof this.activeKeys[key] !== "undefined";
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.activeSpecialKeys[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
registerHook(hook: KeyHook) {
|
||||||
|
this.registeredKeyHooks.push(hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterHook(hook: KeyHook) {
|
||||||
|
this.registeredKeyHooks.remove(hook);
|
||||||
|
this.activeKeyHooks.remove(hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerListener(listener: (event: KeyEvent) => void) {
|
||||||
|
this.registeredListener.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterListener(listener: (event: KeyEvent) => void) {
|
||||||
|
this.registeredListener.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fireKeyEvent(event: KeyEvent) {
|
||||||
|
//console.debug("Trigger key event %o", key_event);
|
||||||
|
for(const listener of this.registeredListener) {
|
||||||
|
listener(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(event.type == EventType.KEY_TYPED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let oldHooks = [...this.activeKeyHooks];
|
||||||
|
let newHooks = [];
|
||||||
|
|
||||||
|
this.activeSpecialKeys[SpecialKey.ALT] = event.keyAlt;
|
||||||
|
this.activeSpecialKeys[SpecialKey.CTRL] = event.keyCtrl;
|
||||||
|
this.activeSpecialKeys[SpecialKey.SHIFT] = event.keyShift;
|
||||||
|
this.activeSpecialKeys[SpecialKey.WINDOWS] = event.keyWindows;
|
||||||
|
|
||||||
|
delete this.activeKeys[event.keyCode];
|
||||||
|
if(event.type == EventType.KEY_PRESS) {
|
||||||
|
this.activeKeys[event.keyCode] = event;
|
||||||
|
|
||||||
|
for(const hook of this.registeredKeyHooks) {
|
||||||
|
if(hook.keyCode !== event.keyCode) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hook.keyAlt != event.keyAlt) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hook.keyCtrl != event.keyCtrl) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hook.keyShift != event.keyShift) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hook.keyWindows != event.keyWindows) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
newHooks.push(hook);
|
||||||
|
if(!oldHooks.remove(hook) && hook.callbackPress) {
|
||||||
|
hook.callbackPress();
|
||||||
|
logTrace(LogCategory.GENERAL, tr("Trigger key press for %o!"), hook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//We have a new situation
|
||||||
|
for(const hook of oldHooks) {
|
||||||
|
//Do not test for meta key states because they could differ in a key release event
|
||||||
|
if(hook.keyCode === event.keyCode) {
|
||||||
|
if(hook.callbackRelease) {
|
||||||
|
hook.callbackRelease();
|
||||||
|
logTrace(LogCategory.GENERAL, tr("Trigger key release for %o!"), hook);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newHooks.push(hook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeKeyHooks = newHooks;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected resetKeyboardState() {
|
||||||
|
this.activeSpecialKeys[SpecialKey.ALT] = false;
|
||||||
|
this.activeSpecialKeys[SpecialKey.CTRL] = false;
|
||||||
|
this.activeSpecialKeys[SpecialKey.SHIFT] = false;
|
||||||
|
this.activeSpecialKeys[SpecialKey.WINDOWS] = false;
|
||||||
|
|
||||||
|
for(const code of Object.keys(this.activeKeys)) {
|
||||||
|
delete this.activeKeys[code];
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const hook of this.activeKeyHooks) {
|
||||||
|
hook.callbackRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeKeyHooks = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let keyBoardBackend: KeyBoardBackend;
|
||||||
|
export function getKeyBoard() : KeyBoardBackend {
|
||||||
|
return keyBoardBackend;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setKeyBoardBackend(newBackend: KeyBoardBackend) {
|
||||||
|
keyBoardBackend = newBackend;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getKeyDescription(key: KeyDescriptor) {
|
||||||
let result = "";
|
let result = "";
|
||||||
if(key.key_shift) {
|
if(key.keyShift) {
|
||||||
result += " + " + tr("Shift");
|
result += " + " + tr("Shift");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(key.key_alt) {
|
if(key.keyAlt) {
|
||||||
result += " + " + tr("Alt");
|
result += " + " + tr("Alt");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(key.key_ctrl) {
|
if(key.keyCtrl) {
|
||||||
result += " + " + tr("CTRL");
|
result += " + " + tr("CTRL");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(key.key_windows) {
|
if(key.keyWindows) {
|
||||||
result += " + " + tr("Win");
|
result += " + " + tr("Win");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(key.key_code) {
|
if(key.keyCode) {
|
||||||
let key_name;
|
let keyName;
|
||||||
if(key.key_code.startsWith("Key")) {
|
if(key.keyCode.startsWith("Key")) {
|
||||||
key_name = key.key_code.substr(3);
|
keyName = key.keyCode.substr(3);
|
||||||
} else if(key.key_code.startsWith("Digit")) {
|
} else if(key.keyCode.startsWith("Digit")) {
|
||||||
key_name = key.key_code.substr(5);
|
keyName = key.keyCode.substr(5);
|
||||||
} else if(key.key_code.startsWith("Numpad")) {
|
} else if(key.keyCode.startsWith("Numpad")) {
|
||||||
key_name = "Numpad " + key.key_code.substr(6);
|
keyName = "Numpad " + key.keyCode.substr(6);
|
||||||
} else {
|
} else {
|
||||||
key_name = key.key_code;
|
keyName = key.keyCode;
|
||||||
}
|
}
|
||||||
result += " + " + key_name;
|
result += " + " + keyName;
|
||||||
}
|
}
|
||||||
return result ? result.substr(3) : tr("unset");
|
return result ? result.substr(3) : tr("unset");
|
||||||
}
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
export interface OutputDevice {
|
||||||
|
device_id: string;
|
||||||
|
|
||||||
|
driver: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AudioBackendEvents {
|
||||||
|
notify_initialized: {},
|
||||||
|
notify_volume_changed: { oldVolume: number, newVolume: number }
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AudioBackend {
|
||||||
|
isInitialized() : boolean;
|
||||||
|
getAudioContext() : AudioContext | undefined;
|
||||||
|
|
||||||
|
isDeviceRefreshAvailable() : boolean;
|
||||||
|
refreshDevices() : Promise<void>;
|
||||||
|
|
||||||
|
getAvailableDevices() : Promise<OutputDevice[]>;
|
||||||
|
getDefaultDeviceId() : string;
|
||||||
|
|
||||||
|
getCurrentDevice() : OutputDevice;
|
||||||
|
setCurrentDevice(targetId: string | undefined) : Promise<void>;
|
||||||
|
|
||||||
|
getMasterVolume() : number;
|
||||||
|
setMasterVolume(volume: number);
|
||||||
|
|
||||||
|
executeWhenInitialized(callback: () => void);
|
||||||
|
}
|
||||||
|
|
||||||
|
let backend: AudioBackend;
|
||||||
|
export function getAudioBackend(): AudioBackend {
|
||||||
|
return backend;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setAudioBackend(newBackend: AudioBackend) {
|
||||||
|
backend = newBackend;
|
||||||
|
}
|
|
@ -4,11 +4,9 @@ import {AbstractInput, LevelMeter} from "../voice/RecorderBase";
|
||||||
import {Registry} from "../events";
|
import {Registry} from "../events";
|
||||||
import {Settings, settings} from "tc-shared/settings";
|
import {Settings, settings} from "tc-shared/settings";
|
||||||
|
|
||||||
export type DeviceQueryResult = {}
|
|
||||||
|
|
||||||
export interface AudioRecorderBacked {
|
export interface AudioRecorderBacked {
|
||||||
createInput() : AbstractInput;
|
createInput() : AbstractInput;
|
||||||
createLevelMeter(device: IDevice) : Promise<LevelMeter>;
|
createLevelMeter(device: InputDevice) : Promise<LevelMeter>;
|
||||||
|
|
||||||
getDeviceList() : DeviceList;
|
getDeviceList() : DeviceList;
|
||||||
|
|
||||||
|
@ -35,14 +33,14 @@ export interface DeviceListEvents {
|
||||||
|
|
||||||
export type DeviceListState = "healthy" | "uninitialized" | "no-permissions" | "error";
|
export type DeviceListState = "healthy" | "uninitialized" | "no-permissions" | "error";
|
||||||
|
|
||||||
export interface IDevice {
|
export interface InputDevice {
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
|
|
||||||
driver: string;
|
driver: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace IDevice {
|
export namespace InputDevice {
|
||||||
export const NoDeviceId = "none";
|
export const NoDeviceId = "none";
|
||||||
export const DefaultDeviceId = "default";
|
export const DefaultDeviceId = "default";
|
||||||
}
|
}
|
||||||
|
@ -60,7 +58,7 @@ export interface DeviceList {
|
||||||
getPermissionState() : PermissionState;
|
getPermissionState() : PermissionState;
|
||||||
|
|
||||||
getStatus() : DeviceListState;
|
getStatus() : DeviceListState;
|
||||||
getDevices() : IDevice[];
|
getDevices() : InputDevice[];
|
||||||
|
|
||||||
getDefaultDeviceId() : string;
|
getDefaultDeviceId() : string;
|
||||||
|
|
||||||
|
@ -144,7 +142,7 @@ export abstract class AbstractDeviceList implements DeviceList {
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract getDefaultDeviceId(): string;
|
abstract getDefaultDeviceId(): string;
|
||||||
abstract getDevices(): IDevice[];
|
abstract getDevices(): InputDevice[];
|
||||||
abstract getEvents(): Registry<DeviceListEvents>;
|
abstract getEvents(): Registry<DeviceListEvents>;
|
||||||
abstract isRefreshAvailable(): boolean;
|
abstract isRefreshAvailable(): boolean;
|
||||||
abstract refresh(): Promise<void>;
|
abstract refresh(): Promise<void>;
|
|
@ -1,8 +1,6 @@
|
||||||
import * as log from "../log";
|
|
||||||
import {LogCategory, logError, logInfo, logWarn} from "../log";
|
import {LogCategory, logError, logInfo, logWarn} from "../log";
|
||||||
import {Settings, settings} from "../settings";
|
import {Settings, settings} from "../settings";
|
||||||
import {ConnectionHandler} from "../ConnectionHandler";
|
import {ConnectionHandler} from "../ConnectionHandler";
|
||||||
import * as sbackend from "tc-backend/audio/sounds";
|
|
||||||
import { tr } from "tc-shared/i18n/localize";
|
import { tr } from "tc-shared/i18n/localize";
|
||||||
|
|
||||||
export enum Sound {
|
export enum Sound {
|
||||||
|
@ -226,7 +224,6 @@ export async function resolve_sound(sound: Sound) : Promise<SoundHandle> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export let manager: SoundManager;
|
export let manager: SoundManager;
|
||||||
|
|
||||||
export class SoundManager {
|
export class SoundManager {
|
||||||
private readonly _handle: ConnectionHandler;
|
private readonly _handle: ConnectionHandler;
|
||||||
private _playing_sounds: {[key: string]:number} = {};
|
private _playing_sounds: {[key: string]:number} = {};
|
||||||
|
@ -256,7 +253,7 @@ export class SoundManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
this._playing_sounds[handle.filename] = (this._playing_sounds[handle.filename] || 0) + 1;
|
this._playing_sounds[handle.filename] = (this._playing_sounds[handle.filename] || 0) + 1;
|
||||||
sbackend.play_sound({
|
getSoundBackend().playSound({
|
||||||
path: "audio/" + handle.filename,
|
path: "audio/" + handle.filename,
|
||||||
volume: volume * master_volume
|
volume: volume * master_volume
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
@ -276,3 +273,16 @@ export class SoundManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SoundBackend {
|
||||||
|
playSound(sound: SoundFile) : Promise<void>;
|
||||||
|
}
|
||||||
|
let soundBackend: SoundBackend;
|
||||||
|
|
||||||
|
export function getSoundBackend() {
|
||||||
|
return soundBackend;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setSoundBackend(newSoundBackend: SoundBackend) {
|
||||||
|
soundBackend = newSoundBackend;
|
||||||
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
export interface Device {
|
|
||||||
device_id: string;
|
|
||||||
|
|
||||||
driver: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {LogCategory, logError, logInfo, logWarn} from "../log";
|
import {LogCategory, logError, logInfo, logWarn} from "../log";
|
||||||
import {AbstractServerConnection, CommandOptions, ServerCommand} from "../connection/ConnectionBase";
|
import {AbstractServerConnection, CommandOptions, ServerCommand} from "../connection/ConnectionBase";
|
||||||
import {Sound} from "../sound/Sounds";
|
import {Sound} from "../audio/Sounds";
|
||||||
import {CommandResult} from "../connection/ServerConnectionDeclaration";
|
import {CommandResult} from "../connection/ServerConnectionDeclaration";
|
||||||
import {createErrorModal, createInfoModal, createInputModal, createModal} from "../ui/elements/Modal";
|
import {createErrorModal, createInfoModal, createInputModal, createModal} from "../ui/elements/Modal";
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -9,9 +9,9 @@ import {RemoteRTPAudioTrack, RemoteRTPTrackState, RemoteRTPVideoTrack, TrackClie
|
||||||
import {SdpCompressor, SdpProcessor} from "./SdpUtils";
|
import {SdpCompressor, SdpProcessor} from "./SdpUtils";
|
||||||
import {ErrorCode} from "tc-shared/connection/ErrorCode";
|
import {ErrorCode} from "tc-shared/connection/ErrorCode";
|
||||||
import {WhisperTarget} from "tc-shared/voice/VoiceWhisper";
|
import {WhisperTarget} from "tc-shared/voice/VoiceWhisper";
|
||||||
import {globalAudioContext} from "tc-backend/audio/player";
|
|
||||||
import {VideoBroadcastConfig, VideoBroadcastType} from "tc-shared/connection/VideoConnection";
|
import {VideoBroadcastConfig, VideoBroadcastType} from "tc-shared/connection/VideoConnection";
|
||||||
import {Settings, settings} from "tc-shared/settings";
|
import {Settings, settings} from "tc-shared/settings";
|
||||||
|
import {getAudioBackend} from "tc-shared/audio/Player";
|
||||||
|
|
||||||
const kSdpCompressionMode = 1;
|
const kSdpCompressionMode = 1;
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ function getIdleTrack(kind: "video" | "audio") : MediaStreamTrack | null {
|
||||||
return dummyVideoTrack;
|
return dummyVideoTrack;
|
||||||
} else if(kind === "audio") {
|
} else if(kind === "audio") {
|
||||||
if(!dummyAudioTrack) {
|
if(!dummyAudioTrack) {
|
||||||
const dest = globalAudioContext().createMediaStreamDestination();
|
const dest = getAudioBackend().getAudioContext().createMediaStreamDestination();
|
||||||
dummyAudioTrack = dest.stream.getAudioTracks()[0];
|
dummyAudioTrack = dest.stream.getAudioTracks()[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {Registry} from "tc-shared/events";
|
import {Registry} from "tc-shared/events";
|
||||||
import {LogCategory, logTrace, logWarn} from "tc-shared/log";
|
import {LogCategory, logTrace, logWarn} from "tc-shared/log";
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
import {globalAudioContext, on_ready} from "tc-backend/audio/player";
|
import {getAudioBackend} from "tc-shared/audio/Player";
|
||||||
|
|
||||||
export interface TrackClientInfo {
|
export interface TrackClientInfo {
|
||||||
media?: number,
|
media?: number,
|
||||||
|
@ -170,13 +170,13 @@ export class RemoteRTPAudioTrack extends RemoteRTPTrack {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
on_ready(() => {
|
getAudioBackend().executeWhenInitialized(() => {
|
||||||
if(!this.mediaStream) {
|
if(!this.mediaStream) {
|
||||||
/* we've already been destroyed */
|
/* we've already been destroyed */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const audioContext = globalAudioContext();
|
const audioContext = getAudioBackend().getAudioContext();
|
||||||
this.audioNode = audioContext.createMediaStreamSource(this.mediaStream);
|
this.audioNode = audioContext.createMediaStreamSource(this.mediaStream);
|
||||||
this.gainNode = audioContext.createGain();
|
this.gainNode = audioContext.createGain();
|
||||||
this.updateGainNode();
|
this.updateGainNode();
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import {Registry} from "../events";
|
import {Registry} from "../events";
|
||||||
import {ClientGlobalControlEvents} from "../events/GlobalEvents";
|
import {ClientGlobalControlEvents} from "../events/GlobalEvents";
|
||||||
import {Sound} from "../sound/Sounds";
|
|
||||||
import {ConnectionHandler} from "../ConnectionHandler";
|
import {ConnectionHandler} from "../ConnectionHandler";
|
||||||
import {createErrorModal, createInfoModal, createInputModal} from "../ui/elements/Modal";
|
import {createErrorModal, createInfoModal, createInputModal} from "../ui/elements/Modal";
|
||||||
import PermissionType from "../permission/PermissionType";
|
import PermissionType from "../permission/PermissionType";
|
||||||
|
@ -20,6 +19,7 @@ import {LogCategory, logError, logWarn} from "tc-shared/log";
|
||||||
import {spawnEchoTestModal} from "tc-shared/ui/modal/echo-test/Controller";
|
import {spawnEchoTestModal} from "tc-shared/ui/modal/echo-test/Controller";
|
||||||
import {spawnConnectModalNew} from "tc-shared/ui/modal/connect/Controller";
|
import {spawnConnectModalNew} from "tc-shared/ui/modal/connect/Controller";
|
||||||
import {spawnBookmarkModal} from "tc-shared/ui/modal/bookmarks/Controller";
|
import {spawnBookmarkModal} from "tc-shared/ui/modal/bookmarks/Controller";
|
||||||
|
import {Sound} from "tc-shared/audio/Sounds";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
function initialize_sounds(event_registry: Registry<ClientGlobalControlEvents>) {
|
function initialize_sounds(event_registry: Registry<ClientGlobalControlEvents>) {
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
import * as loader from "tc-loader";
|
import * as loader from "tc-loader";
|
||||||
import {Stage} from "tc-loader";
|
|
||||||
import * as bipc from "./ipc/BrowserIPC";
|
import * as bipc from "./ipc/BrowserIPC";
|
||||||
import * as sound from "./sound/Sounds";
|
import * as sound from "./audio/Sounds";
|
||||||
import * as i18n from "./i18n/localize";
|
import * as i18n from "./i18n/localize";
|
||||||
import {tra} from "./i18n/localize";
|
import {tra} from "./i18n/localize";
|
||||||
import * as fidentity from "./profiles/identities/TeaForumIdentity";
|
import * as fidentity from "./profiles/identities/TeaForumIdentity";
|
||||||
import * as aplayer from "tc-backend/audio/player";
|
|
||||||
import * as ppt from "tc-backend/ppt";
|
|
||||||
import * as global_ev_handler from "./events/ClientGlobalControlHandler";
|
import * as global_ev_handler from "./events/ClientGlobalControlHandler";
|
||||||
import {AppParameters, settings, Settings, UrlParameterBuilder, UrlParameterParser} from "tc-shared/settings";
|
import {AppParameters, settings, Settings, UrlParameterBuilder, UrlParameterParser} from "tc-shared/settings";
|
||||||
import {LogCategory, logDebug, logError, logInfo, logWarn} from "tc-shared/log";
|
import {LogCategory, logDebug, logError, logInfo, logWarn} from "tc-shared/log";
|
||||||
|
@ -55,6 +52,7 @@ import "./ui/elements/Tab";
|
||||||
import "./clientservice";
|
import "./clientservice";
|
||||||
import "./text/bbcode/InviteController";
|
import "./text/bbcode/InviteController";
|
||||||
import "./text/bbcode/YoutubeController";
|
import "./text/bbcode/YoutubeController";
|
||||||
|
import {getAudioBackend} from "tc-shared/audio/Player";
|
||||||
|
|
||||||
assertMainApplication();
|
assertMainApplication();
|
||||||
|
|
||||||
|
@ -73,12 +71,7 @@ async function initialize() {
|
||||||
|
|
||||||
async function initializeApp() {
|
async function initializeApp() {
|
||||||
global_ev_handler.initialize(global_client_actions);
|
global_ev_handler.initialize(global_client_actions);
|
||||||
|
getAudioBackend().setMasterVolume(settings.getValue(Settings.KEY_SOUND_MASTER) / 100);
|
||||||
if(!aplayer.initialize()) {
|
|
||||||
console.warn(tr("Failed to initialize audio controller!"));
|
|
||||||
}
|
|
||||||
|
|
||||||
aplayer.on_ready(() => aplayer.set_master_volume(settings.getValue(Settings.KEY_SOUND_MASTER) / 100));
|
|
||||||
|
|
||||||
const recorder = new RecorderProfile("default");
|
const recorder = new RecorderProfile("default");
|
||||||
try {
|
try {
|
||||||
|
@ -93,14 +86,6 @@ async function initializeApp() {
|
||||||
logInfo(LogCategory.AUDIO, tr("Sounds initialized"));
|
logInfo(LogCategory.AUDIO, tr("Sounds initialized"));
|
||||||
});
|
});
|
||||||
sound.set_master_volume(settings.getValue(Settings.KEY_SOUND_MASTER_SOUNDS) / 100);
|
sound.set_master_volume(settings.getValue(Settings.KEY_SOUND_MASTER_SOUNDS) / 100);
|
||||||
|
|
||||||
try {
|
|
||||||
await ppt.initialize();
|
|
||||||
} catch(error) {
|
|
||||||
logError(LogCategory.GENERAL, tr("Failed to initialize ppt!\nError: %o"), error);
|
|
||||||
loader.critical_error(tr("Failed to initialize ppt!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The native client has received a connect request. */
|
/* The native client has received a connect request. */
|
||||||
|
@ -200,7 +185,7 @@ async function doHandleConnectRequest(serverAddress: string, serverUniqueId: str
|
||||||
return { status: "profile-invalid" };
|
return { status: "profile-invalid" };
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!aplayer.initialized()) {
|
if(!getAudioBackend().isInitialized()) {
|
||||||
/* Trick the client into clicking somewhere on the site to initialize audio */
|
/* Trick the client into clicking somewhere on the site to initialize audio */
|
||||||
const resultPromise = new Promise<boolean>(resolve => {
|
const resultPromise = new Promise<boolean>(resolve => {
|
||||||
spawnYesNo(tra("Connect to {}", serverAddress), tra("Would you like to connect to {}?", serverAddress), resolve).open();
|
spawnYesNo(tra("Connect to {}", serverAddress), tra("Would you like to connect to {}?", serverAddress), resolve).open();
|
||||||
|
@ -211,7 +196,7 @@ async function doHandleConnectRequest(serverAddress: string, serverUniqueId: str
|
||||||
return { status: "client-aborted" };
|
return { status: "client-aborted" };
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise(resolve => aplayer.on_ready(resolve));
|
await new Promise(resolve => getAudioBackend().executeWhenInitialized(resolve));
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientNickname = parameters.getValue(AppParameters.KEY_CONNECT_NICKNAME, undefined);
|
const clientNickname = parameters.getValue(AppParameters.KEY_CONNECT_NICKNAME, undefined);
|
||||||
|
@ -403,15 +388,6 @@ const task_teaweb_starter: loader.Task = {
|
||||||
try {
|
try {
|
||||||
await initializeApp();
|
await initializeApp();
|
||||||
main();
|
main();
|
||||||
if(!aplayer.initialized()) {
|
|
||||||
logInfo(LogCategory.VOICE, tr("Initialize audio controller later!"));
|
|
||||||
if(!aplayer.initializeFromGesture) {
|
|
||||||
console.error(tr("Missing aplayer.initializeFromGesture"));
|
|
||||||
} else {
|
|
||||||
$(document).one('click', () => aplayer.initializeFromGesture());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loader.config.abortAnimationOnFinish = settings.getValue(Settings.KEY_LOADER_ANIMATION_ABORT);
|
loader.config.abortAnimationOnFinish = settings.getValue(Settings.KEY_LOADER_ANIMATION_ABORT);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.error(ex.stack);
|
console.error(ex.stack);
|
||||||
|
|
|
@ -6,10 +6,9 @@ import {PermissionType} from "../permission/PermissionType";
|
||||||
import {settings, Settings} from "../settings";
|
import {settings, Settings} from "../settings";
|
||||||
import * as contextmenu from "../ui/elements/ContextMenu";
|
import * as contextmenu from "../ui/elements/ContextMenu";
|
||||||
import {MenuEntryType} from "../ui/elements/ContextMenu";
|
import {MenuEntryType} from "../ui/elements/ContextMenu";
|
||||||
import {Sound} from "../sound/Sounds";
|
import {Sound} from "../audio/Sounds";
|
||||||
import {createErrorModal, createInfoModal, createInputModal} from "../ui/elements/Modal";
|
import {createErrorModal, createInfoModal, createInputModal} from "../ui/elements/Modal";
|
||||||
import {CommandResult} from "../connection/ServerConnectionDeclaration";
|
import {CommandResult} from "../connection/ServerConnectionDeclaration";
|
||||||
import * as htmltags from "../ui/htmltags";
|
|
||||||
import {hashPassword} from "../utils/helpers";
|
import {hashPassword} from "../utils/helpers";
|
||||||
import {openChannelInfo} from "../ui/modal/ModalChannelInfo";
|
import {openChannelInfo} from "../ui/modal/ModalChannelInfo";
|
||||||
import {formatMessage} from "../ui/frames/chat";
|
import {formatMessage} from "../ui/frames/chat";
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
|
||||||
import {MenuEntryType} from "tc-shared/ui/elements/ContextMenu";
|
import {MenuEntryType} from "tc-shared/ui/elements/ContextMenu";
|
||||||
import {LogCategory, logDebug, logError, logWarn} from "tc-shared/log";
|
import {LogCategory, logDebug, logError, logWarn} from "tc-shared/log";
|
||||||
import {PermissionType} from "tc-shared/permission/PermissionType";
|
import {PermissionType} from "tc-shared/permission/PermissionType";
|
||||||
import {Sound} from "tc-shared/sound/Sounds";
|
import {Sound} from "tc-shared/audio/Sounds";
|
||||||
import {Group} from "tc-shared/permission/GroupManager";
|
import {Group} from "tc-shared/permission/GroupManager";
|
||||||
import {ServerAddress, ServerEntry} from "./Server";
|
import {ServerAddress, ServerEntry} from "./Server";
|
||||||
import {ChannelEntry, ChannelProperties, ChannelSubscribeMode} from "./Channel";
|
import {ChannelEntry, ChannelProperties, ChannelSubscribeMode} from "./Channel";
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {ChannelTree} from "./ChannelTree";
|
||||||
import * as log from "../log";
|
import * as log from "../log";
|
||||||
import {LogCategory, logDebug, logError, logInfo, LogType} from "../log";
|
import {LogCategory, logDebug, logError, logInfo, LogType} from "../log";
|
||||||
import {Settings, settings} from "../settings";
|
import {Settings, settings} from "../settings";
|
||||||
import {Sound} from "../sound/Sounds";
|
import {Sound} from "../audio/Sounds";
|
||||||
import {Group, GroupManager, GroupTarget, GroupType} from "../permission/GroupManager";
|
import {Group, GroupManager, GroupTarget, GroupType} from "../permission/GroupManager";
|
||||||
import PermissionType from "../permission/PermissionType";
|
import PermissionType from "../permission/PermissionType";
|
||||||
import {createErrorModal, createInputModal} from "../ui/elements/Modal";
|
import {createErrorModal, createInputModal} from "../ui/elements/Modal";
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {Settings, settings} from "../settings";
|
||||||
import * as contextmenu from "../ui/elements/ContextMenu";
|
import * as contextmenu from "../ui/elements/ContextMenu";
|
||||||
import * as log from "../log";
|
import * as log from "../log";
|
||||||
import {LogCategory, logInfo, LogType} from "../log";
|
import {LogCategory, logInfo, LogType} from "../log";
|
||||||
import {Sound} from "../sound/Sounds";
|
import {Sound} from "../audio/Sounds";
|
||||||
import {openServerInfo} from "../ui/modal/ModalServerInfo";
|
import {openServerInfo} from "../ui/modal/ModalServerInfo";
|
||||||
import {createServerModal} from "../ui/modal/ModalServerEdit";
|
import {createServerModal} from "../ui/modal/ModalServerEdit";
|
||||||
import {spawnIconSelect} from "../ui/modal/ModalIconSelect";
|
import {spawnIconSelect} from "../ui/modal/ModalIconSelect";
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {VideoBroadcastType, VideoConnectionStatus} from "tc-shared/connection/Vi
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
import {getVideoDriver} from "tc-shared/video/VideoSource";
|
import {getVideoDriver} from "tc-shared/video/VideoSource";
|
||||||
import {kLocalBroadcastChannels} from "tc-shared/ui/frames/video/Definitions";
|
import {kLocalBroadcastChannels} from "tc-shared/ui/frames/video/Definitions";
|
||||||
import {getRecorderBackend, IDevice} from "tc-shared/audio/recorder";
|
import {getRecorderBackend, InputDevice} from "tc-shared/audio/Recorder";
|
||||||
import {defaultRecorder, defaultRecorderEvents} from "tc-shared/voice/RecorderProfile";
|
import {defaultRecorder, defaultRecorderEvents} from "tc-shared/voice/RecorderProfile";
|
||||||
import {bookmarks} from "tc-shared/Bookmarks";
|
import {bookmarks} from "tc-shared/Bookmarks";
|
||||||
import {connectionHistory} from "tc-shared/connectionlog/History";
|
import {connectionHistory} from "tc-shared/connectionlog/History";
|
||||||
|
@ -276,7 +276,7 @@ class InfoController {
|
||||||
this.events.fire_react("notify_microphone_list", {
|
this.events.fire_react("notify_microphone_list", {
|
||||||
devices: devices.map(device => {
|
devices: devices.map(device => {
|
||||||
let selected = false;
|
let selected = false;
|
||||||
if(selectedDevice === IDevice.DefaultDeviceId && device.deviceId === defaultDevice) {
|
if(selectedDevice === InputDevice.DefaultDeviceId && device.deviceId === defaultDevice) {
|
||||||
selected = true;
|
selected = true;
|
||||||
} else if(selectedDevice === device.deviceId) {
|
} else if(selectedDevice === device.deviceId) {
|
||||||
selected = true;
|
selected = true;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import {createModal} from "../../ui/elements/Modal";
|
import {createModal} from "../../ui/elements/Modal";
|
||||||
import {EventType, key_description, KeyEvent} from "../../PPTListener";
|
import {EventType, getKeyBoard, getKeyDescription, KeyEvent} from "../../PPTListener";
|
||||||
import * as ppt from "tc-backend/ppt";
|
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
|
|
||||||
export function spawnKeySelect(callback: (key?: KeyEvent) => void) {
|
export function spawnKeySelect(callback: (key?: KeyEvent) => void) {
|
||||||
|
@ -27,7 +26,7 @@ export function spawnKeySelect(callback: (key?: KeyEvent) => void) {
|
||||||
current_key = event;
|
current_key = event;
|
||||||
current_key_age = Date.now();
|
current_key_age = Date.now();
|
||||||
|
|
||||||
container_key.text(key_description(event));
|
container_key.text(getKeyDescription(event));
|
||||||
button_save.prop("disabled", false);
|
button_save.prop("disabled", false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -36,7 +35,7 @@ export function spawnKeySelect(callback: (key?: KeyEvent) => void) {
|
||||||
button_save.on('click', () => {
|
button_save.on('click', () => {
|
||||||
if (__build.version !== "web") {
|
if (__build.version !== "web") {
|
||||||
/* Because pressing the close button is also a mouse action */
|
/* Because pressing the close button is also a mouse action */
|
||||||
if (current_key_age + 1000 > Date.now() && current_key.key_code == "MOUSE1")
|
if (current_key_age + 1000 > Date.now() && current_key.keyCode == "MOUSE1")
|
||||||
current_key = last_key;
|
current_key = last_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,8 +44,9 @@ export function spawnKeySelect(callback: (key?: KeyEvent) => void) {
|
||||||
}).prop("disabled", true);
|
}).prop("disabled", true);
|
||||||
button_cancel.on('click', () => modal.close());
|
button_cancel.on('click', () => modal.close());
|
||||||
|
|
||||||
ppt.register_key_listener(listener);
|
const keyboard = getKeyBoard();
|
||||||
modal.close_listener.push(() => ppt.unregister_key_listener(listener));
|
keyboard.registerListener(listener);
|
||||||
|
modal.close_listener.push(() => keyboard.unregisterListener(listener));
|
||||||
|
|
||||||
modal.htmlTag.find(".modal-body").addClass("modal-keyselect modal-green");
|
modal.htmlTag.find(".modal-body").addClass("modal-keyselect modal-green");
|
||||||
modal.open();
|
modal.open();
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import {createErrorModal, createInfoModal, createInputModal, createModal, Modal} from "tc-shared/ui/elements/Modal";
|
import {createErrorModal, createInfoModal, createInputModal, createModal, Modal} from "tc-shared/ui/elements/Modal";
|
||||||
import {sliderfy} from "tc-shared/ui/elements/Slider";
|
import {sliderfy} from "tc-shared/ui/elements/Slider";
|
||||||
import {settings, Settings} from "tc-shared/settings";
|
import {settings, Settings} from "tc-shared/settings";
|
||||||
import * as sound from "tc-shared/sound/Sounds";
|
import * as sound from "tc-shared/audio/Sounds";
|
||||||
import {manager, set_master_volume, Sound} from "tc-shared/sound/Sounds";
|
import {manager, set_master_volume, Sound} from "tc-shared/audio/Sounds";
|
||||||
import * as profiles from "tc-shared/profiles/ConnectionProfile";
|
import * as profiles from "tc-shared/profiles/ConnectionProfile";
|
||||||
import {ConnectionProfile} from "tc-shared/profiles/ConnectionProfile";
|
import {ConnectionProfile} from "tc-shared/profiles/ConnectionProfile";
|
||||||
import {IdentitifyType} from "tc-shared/profiles/Identity";
|
import {IdentitifyType} from "tc-shared/profiles/Identity";
|
||||||
|
@ -18,8 +18,7 @@ import * as i18nc from "tc-shared/i18n/country";
|
||||||
import * as forum from "tc-shared/profiles/identities/teaspeak-forum";
|
import * as forum from "tc-shared/profiles/identities/teaspeak-forum";
|
||||||
import {formatMessage, set_icon_size} from "tc-shared/ui/frames/chat";
|
import {formatMessage, set_icon_size} from "tc-shared/ui/frames/chat";
|
||||||
import {spawnTeamSpeakIdentityImport, spawnTeamSpeakIdentityImprove} from "tc-shared/ui/modal/ModalIdentity";
|
import {spawnTeamSpeakIdentityImport, spawnTeamSpeakIdentityImprove} from "tc-shared/ui/modal/ModalIdentity";
|
||||||
import {Device} from "tc-shared/audio/player";
|
import {getAudioBackend, OutputDevice} from "tc-shared/audio/Player";
|
||||||
import * as aplayer from "tc-backend/audio/player";
|
|
||||||
import {KeyMapSettings} from "tc-shared/ui/modal/settings/Keymap";
|
import {KeyMapSettings} from "tc-shared/ui/modal/settings/Keymap";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
|
@ -628,8 +627,8 @@ function settings_audio_speaker(container: JQuery, modal: Modal) {
|
||||||
const update_devices = () => {
|
const update_devices = () => {
|
||||||
container_devices.children().remove();
|
container_devices.children().remove();
|
||||||
|
|
||||||
const current_selected = aplayer.current_device();
|
const current_selected = getAudioBackend().getCurrentDevice();
|
||||||
const generate_device = (device: Device | undefined) => {
|
const generate_device = (device: OutputDevice | undefined) => {
|
||||||
const selected = device === current_selected || (typeof (current_selected) !== "undefined" && typeof (device) !== "undefined" && current_selected.device_id == device.device_id);
|
const selected = device === current_selected || (typeof (current_selected) !== "undefined" && typeof (device) !== "undefined" && current_selected.device_id == device.device_id);
|
||||||
|
|
||||||
const tag = $.spawn("div").addClass("device").toggleClass("selected", selected).append(
|
const tag = $.spawn("div").addClass("device").toggleClass("selected", selected).append(
|
||||||
|
@ -654,7 +653,7 @@ function settings_audio_speaker(container: JQuery, modal: Modal) {
|
||||||
_old.removeClass("selected");
|
_old.removeClass("selected");
|
||||||
tag.addClass("selected");
|
tag.addClass("selected");
|
||||||
|
|
||||||
aplayer.set_device(device ? device.device_id : null).then(() => {
|
getAudioBackend().setCurrentDevice(device?.device_id).then(() => {
|
||||||
logDebug(LogCategory.AUDIO, tr("Changed default speaker device"));
|
logDebug(LogCategory.AUDIO, tr("Changed default speaker device"));
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
_old.addClass("selected");
|
_old.addClass("selected");
|
||||||
|
@ -669,7 +668,7 @@ function settings_audio_speaker(container: JQuery, modal: Modal) {
|
||||||
};
|
};
|
||||||
|
|
||||||
generate_device(undefined).appendTo(container_devices);
|
generate_device(undefined).appendTo(container_devices);
|
||||||
aplayer.available_devices().then(result => {
|
getAudioBackend().getAvailableDevices().then(result => {
|
||||||
contianer_error.text("").hide();
|
contianer_error.text("").hide();
|
||||||
result.forEach(e => generate_device(e).appendTo(container_devices));
|
result.forEach(e => generate_device(e).appendTo(container_devices));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
|
@ -710,8 +709,7 @@ function settings_audio_speaker(container: JQuery, modal: Modal) {
|
||||||
slider.on('change', event => {
|
slider.on('change', event => {
|
||||||
const volume = parseInt(slider.attr('value'));
|
const volume = parseInt(slider.attr('value'));
|
||||||
|
|
||||||
if (aplayer.set_master_volume)
|
getAudioBackend().setMasterVolume(volume / 100);
|
||||||
aplayer.set_master_volume(volume / 100);
|
|
||||||
settings.setValue(Settings.KEY_SOUND_MASTER, volume);
|
settings.setValue(Settings.KEY_SOUND_MASTER, volume);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,7 @@ class KeyActionEntry extends ReactComponentBase<KeyActionEntryProperties, KeyAct
|
||||||
} else if (this.state.state === "loaded") {
|
} else if (this.state.state === "loaded") {
|
||||||
rightItem = null;
|
rightItem = null;
|
||||||
if (this.state.assignedKey)
|
if (this.state.assignedKey)
|
||||||
rightItem = <div className={cssStyle.key}>{ppt.key_description(this.state.assignedKey)}</div>;
|
rightItem = <div className={cssStyle.key}>{ppt.getKeyDescription(this.state.assignedKey)}</div>;
|
||||||
} else {
|
} else {
|
||||||
rightItem =
|
rightItem =
|
||||||
<div key={"status-error"} className={this.classList(cssStyle.status, cssStyle.error)}><Translatable
|
<div key={"status-error"} className={this.classList(cssStyle.status, cssStyle.error)}><Translatable
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import * as aplayer from "tc-backend/audio/player";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {Registry} from "tc-shared/events";
|
import {Registry} from "tc-shared/events";
|
||||||
import {LevelMeter} from "tc-shared/voice/RecorderBase";
|
import {LevelMeter} from "tc-shared/voice/RecorderBase";
|
||||||
import {LogCategory, logTrace, logWarn} from "tc-shared/log";
|
import {LogCategory, logTrace, logWarn} from "tc-shared/log";
|
||||||
import {defaultRecorder} from "tc-shared/voice/RecorderProfile";
|
import {defaultRecorder} from "tc-shared/voice/RecorderProfile";
|
||||||
import {DeviceListState, getRecorderBackend, IDevice} from "tc-shared/audio/recorder";
|
import {DeviceListState, getRecorderBackend, InputDevice} from "tc-shared/audio/Recorder";
|
||||||
import {Settings, settings} from "tc-shared/settings";
|
import {Settings, settings} from "tc-shared/settings";
|
||||||
import {getBackend} from "tc-shared/backend";
|
import {getBackend} from "tc-shared/backend";
|
||||||
import * as _ from "lodash";
|
import * as _ from "lodash";
|
||||||
|
import {getAudioBackend} from "tc-shared/audio/Player";
|
||||||
|
|
||||||
export type MicrophoneSetting =
|
export type MicrophoneSetting =
|
||||||
"volume"
|
"volume"
|
||||||
|
@ -223,9 +223,9 @@ export function initialize_audio_microphone_controller(events: Registry<Micropho
|
||||||
{
|
{
|
||||||
const currentSelectedDevice = (): SelectedMicrophone => {
|
const currentSelectedDevice = (): SelectedMicrophone => {
|
||||||
let deviceId = defaultRecorder.getDeviceId();
|
let deviceId = defaultRecorder.getDeviceId();
|
||||||
if(deviceId === IDevice.DefaultDeviceId) {
|
if(deviceId === InputDevice.DefaultDeviceId) {
|
||||||
return { type: "default" };
|
return { type: "default" };
|
||||||
} else if(deviceId === IDevice.NoDeviceId) {
|
} else if(deviceId === InputDevice.NoDeviceId) {
|
||||||
return { type: "none" };
|
return { type: "none" };
|
||||||
} else {
|
} else {
|
||||||
return { type: "device", deviceId: deviceId };
|
return { type: "device", deviceId: deviceId };
|
||||||
|
@ -233,7 +233,7 @@ export function initialize_audio_microphone_controller(events: Registry<Micropho
|
||||||
};
|
};
|
||||||
|
|
||||||
events.on("query_devices", event => {
|
events.on("query_devices", event => {
|
||||||
if (!aplayer.initialized()) {
|
if (!getAudioBackend().isInitialized()) {
|
||||||
events.fire_react("notify_devices", {
|
events.fire_react("notify_devices", {
|
||||||
status: "audio-not-initialized"
|
status: "audio-not-initialized"
|
||||||
});
|
});
|
||||||
|
@ -452,10 +452,8 @@ export function initialize_audio_microphone_controller(events: Registry<Micropho
|
||||||
events.fire("query_devices");
|
events.fire("query_devices");
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (!aplayer.initialized()) {
|
if(!getAudioBackend().isInitialized()) {
|
||||||
aplayer.on_ready(() => {
|
getAudioBackend().executeWhenInitialized(() => events.fire_react("query_devices"));
|
||||||
events.fire_react("query_devices");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,12 +11,12 @@ import {createErrorModal} from "tc-shared/ui/elements/Modal";
|
||||||
import {Slider} from "tc-shared/ui/react-elements/Slider";
|
import {Slider} from "tc-shared/ui/react-elements/Slider";
|
||||||
import {RadioButton} from "tc-shared/ui/react-elements/RadioButton";
|
import {RadioButton} from "tc-shared/ui/react-elements/RadioButton";
|
||||||
import {VadType} from "tc-shared/voice/RecorderProfile";
|
import {VadType} from "tc-shared/voice/RecorderProfile";
|
||||||
import {key_description, KeyDescriptor} from "tc-shared/PPTListener";
|
import {getKeyDescription, KeyDescriptor} from "tc-shared/PPTListener";
|
||||||
import {spawnKeySelect} from "tc-shared/ui/modal/ModalKeySelect";
|
import {spawnKeySelect} from "tc-shared/ui/modal/ModalKeySelect";
|
||||||
import {Checkbox} from "tc-shared/ui/react-elements/Checkbox";
|
import {Checkbox} from "tc-shared/ui/react-elements/Checkbox";
|
||||||
import {BoxedInputField} from "tc-shared/ui/react-elements/InputField";
|
import {BoxedInputField} from "tc-shared/ui/react-elements/InputField";
|
||||||
import {IDevice} from "tc-shared/audio/recorder";
|
|
||||||
import {HighlightContainer, HighlightRegion, HighlightText} from "./Heighlight";
|
import {HighlightContainer, HighlightRegion, HighlightText} from "./Heighlight";
|
||||||
|
import {InputDevice} from "tc-shared/audio/Recorder";
|
||||||
|
|
||||||
const cssStyle = require("./Microphone.scss");
|
const cssStyle = require("./Microphone.scss");
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ const Microphone = (props: { events: Registry<MicrophoneSettingsEvents>, device:
|
||||||
<div className={cssStyle.name}>{props.device.name}</div>
|
<div className={cssStyle.name}>{props.device.name}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={cssStyle.containerActivity}>
|
<div className={cssStyle.containerActivity}>
|
||||||
{props.device.id === IDevice.NoDeviceId ? undefined :
|
{props.device.id === InputDevice.NoDeviceId ? undefined :
|
||||||
<ActivityBar key={"a"} events={props.events} deviceId={props.device.id}/>
|
<ActivityBar key={"a"} events={props.events} deviceId={props.device.id}/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -401,7 +401,7 @@ const PPTKeyButton = React.memo((props: { events: Registry<MicrophoneSettingsEve
|
||||||
props.events.fire("action_set_setting", {setting: "ppt-key", value: key});
|
props.events.fire("action_set_setting", {setting: "ppt-key", value: key});
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>{key_description(key)}</Button>;
|
>{getKeyDescription(key)}</Button>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,7 @@ import {CommandResult} from "../../../connection/ServerConnectionDeclaration";
|
||||||
import PermissionType from "../../../permission/PermissionType";
|
import PermissionType from "../../../permission/PermissionType";
|
||||||
import {LogCategory, logError, logTrace} from "../../../log";
|
import {LogCategory, logError, logTrace} from "../../../log";
|
||||||
import {Entry, MenuEntry, MenuEntryType, spawn_context_menu} from "../../../ui/elements/ContextMenu";
|
import {Entry, MenuEntry, MenuEntryType, spawn_context_menu} from "../../../ui/elements/ContextMenu";
|
||||||
import * as ppt from "tc-backend/ppt";
|
import {getKeyBoard, SpecialKey} from "../../../PPTListener";
|
||||||
import {SpecialKey} from "../../../PPTListener";
|
|
||||||
import {spawnYesNo} from "../../../ui/modal/ModalYesNo";
|
import {spawnYesNo} from "../../../ui/modal/ModalYesNo";
|
||||||
import {tr, tra, traj} from "../../../i18n/localize";
|
import {tr, tra, traj} from "../../../i18n/localize";
|
||||||
import {
|
import {
|
||||||
|
@ -427,7 +426,7 @@ export function initializeRemoteFileBrowserController(connection: ConnectionHand
|
||||||
icon_class: "client-file_refresh"
|
icon_class: "client-file_refresh"
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const forceDelete = ppt.key_pressed(SpecialKey.SHIFT);
|
const forceDelete = getKeyBoard().isKeyPressed(SpecialKey.SHIFT);
|
||||||
if (selection.length === 0) {
|
if (selection.length === 0) {
|
||||||
entries.push({
|
entries.push({
|
||||||
type: MenuEntryType.ENTRY,
|
type: MenuEntryType.ENTRY,
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import {EventHandler, ReactEventHandler, Registry} from "tc-shared/events";
|
import {EventHandler, ReactEventHandler, Registry} from "tc-shared/events";
|
||||||
import {useContext, useEffect, useRef, useState} from "react";
|
import {useContext, useEffect, useRef, useState} from "react";
|
||||||
import {FileType} from "tc-shared/file/FileManager";
|
import {FileType} from "tc-shared/file/FileManager";
|
||||||
import * as ppt from "tc-backend/ppt";
|
import {getKeyBoard, SpecialKey} from "tc-shared/PPTListener";
|
||||||
import {SpecialKey} from "tc-shared/PPTListener";
|
|
||||||
import {createErrorModal} from "tc-shared/ui/elements/Modal";
|
import {createErrorModal} from "tc-shared/ui/elements/Modal";
|
||||||
import {tra} from "tc-shared/i18n/localize";
|
import {tra} from "tc-shared/i18n/localize";
|
||||||
import {network} from "tc-shared/ui/frames/chat";
|
import {network} from "tc-shared/ui/frames/chat";
|
||||||
|
@ -422,7 +421,7 @@ const FileName = (props: { path: string, file: ListedFileInfo }) => {
|
||||||
if (props.file.virtual || props.file.mode === "creating" || props.file.mode === "uploading")
|
if (props.file.virtual || props.file.mode === "creating" || props.file.mode === "uploading")
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!ppt.key_pressed(SpecialKey.SHIFT))
|
if (!getKeyBoard().isKeyPressed(SpecialKey.SHIFT))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
@ -656,7 +655,7 @@ const FileListEntry = (props: { row: TableRow<ListedFileInfo>, columns: TableCol
|
||||||
|
|
||||||
onClick={() => props.events.fire("action_select_files", {
|
onClick={() => props.events.fire("action_select_files", {
|
||||||
files: [{name: file.name, type: file.type}],
|
files: [{name: file.name, type: file.type}],
|
||||||
mode: ppt.key_pressed(SpecialKey.SHIFT) ? "toggle" : "exclusive"
|
mode: getKeyBoard().isKeyPressed(SpecialKey.SHIFT) ? "toggle" : "exclusive"
|
||||||
})}
|
})}
|
||||||
onContextMenu={e => {
|
onContextMenu={e => {
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
|
@ -664,7 +663,7 @@ const FileListEntry = (props: { row: TableRow<ListedFileInfo>, columns: TableCol
|
||||||
/* explicitly clicked on one file */
|
/* explicitly clicked on one file */
|
||||||
props.events.fire("action_select_files", {
|
props.events.fire("action_select_files", {
|
||||||
files: [{name: file.name, type: file.type}],
|
files: [{name: file.name, type: file.type}],
|
||||||
mode: ppt.key_pressed(SpecialKey.SHIFT) ? "toggle" : "exclusive"
|
mode: getKeyBoard().isKeyPressed(SpecialKey.SHIFT) ? "toggle" : "exclusive"
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
props.events.fire("action_select_files", {files: [], mode: "exclusive"});
|
props.events.fire("action_select_files", {files: [], mode: "exclusive"});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {IDevice} from "../audio/recorder";
|
import {InputDevice} from "../audio/Recorder";
|
||||||
import {Registry} from "../events";
|
import {Registry} from "../events";
|
||||||
import {Filter, FilterType, FilterTypeClass} from "../voice/Filter";
|
import {Filter, FilterType, FilterTypeClass} from "../voice/Filter";
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ export interface AbstractInput {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LevelMeter {
|
export interface LevelMeter {
|
||||||
getDevice() : IDevice;
|
getDevice() : InputDevice;
|
||||||
|
|
||||||
setObserver(callback: (value: number) => any);
|
setObserver(callback: (value: number) => any);
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
import * as log from "../log";
|
|
||||||
import {LogCategory, logDebug, logError, logWarn} from "../log";
|
import {LogCategory, logDebug, logError, logWarn} from "../log";
|
||||||
import {AbstractInput, FilterMode} from "../voice/RecorderBase";
|
import {AbstractInput, FilterMode} from "../voice/RecorderBase";
|
||||||
import {KeyDescriptor, KeyHook} from "../PPTListener";
|
import {getKeyBoard, KeyDescriptor, KeyHook} from "../PPTListener";
|
||||||
import {Settings, settings} from "../settings";
|
import {Settings, settings} from "../settings";
|
||||||
import {ConnectionHandler} from "../ConnectionHandler";
|
import {ConnectionHandler} from "../ConnectionHandler";
|
||||||
import * as aplayer from "tc-backend/audio/player";
|
import {getRecorderBackend, InputDevice} from "../audio/Recorder";
|
||||||
import * as ppt from "tc-backend/ppt";
|
|
||||||
import {getRecorderBackend, IDevice} from "../audio/recorder";
|
|
||||||
import {FilterType, StateFilter, ThresholdFilter} from "../voice/Filter";
|
import {FilterType, StateFilter, ThresholdFilter} from "../voice/Filter";
|
||||||
import { tr } from "tc-shared/i18n/localize";
|
import { tr } from "tc-shared/i18n/localize";
|
||||||
import {Registry} from "tc-shared/events";
|
import {Registry} from "tc-shared/events";
|
||||||
|
import {getAudioBackend} from "tc-shared/audio/Player";
|
||||||
|
|
||||||
export type VadType = "threshold" | "push_to_talk" | "active";
|
export type VadType = "threshold" | "push_to_talk" | "active";
|
||||||
export interface RecorderProfileConfig {
|
export interface RecorderProfileConfig {
|
||||||
|
@ -85,7 +83,7 @@ export class RecorderProfile {
|
||||||
this.volatile = typeof(volatile) === "boolean" ? volatile : false;
|
this.volatile = typeof(volatile) === "boolean" ? volatile : false;
|
||||||
|
|
||||||
this.pptHook = {
|
this.pptHook = {
|
||||||
callback_release: () => {
|
callbackRelease: () => {
|
||||||
if(this.pptTimeout)
|
if(this.pptTimeout)
|
||||||
clearTimeout(this.pptTimeout);
|
clearTimeout(this.pptTimeout);
|
||||||
|
|
||||||
|
@ -94,14 +92,12 @@ export class RecorderProfile {
|
||||||
}, Math.max(this.config.vad_push_to_talk.delay, 0));
|
}, Math.max(this.config.vad_push_to_talk.delay, 0));
|
||||||
},
|
},
|
||||||
|
|
||||||
callback_press: () => {
|
callbackPress: () => {
|
||||||
if(this.pptTimeout)
|
if(this.pptTimeout)
|
||||||
clearTimeout(this.pptTimeout);
|
clearTimeout(this.pptTimeout);
|
||||||
|
|
||||||
this.registeredFilter["ppt-gate"]?.setState(false);
|
this.registeredFilter["ppt-gate"]?.setState(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
cancel: false
|
|
||||||
} as KeyHook;
|
} as KeyHook;
|
||||||
this.pptHookRegistered = false;
|
this.pptHookRegistered = false;
|
||||||
}
|
}
|
||||||
|
@ -125,7 +121,7 @@ export class RecorderProfile {
|
||||||
/* default values */
|
/* default values */
|
||||||
this.config = {
|
this.config = {
|
||||||
version: 1,
|
version: 1,
|
||||||
device_id: IDevice.DefaultDeviceId,
|
device_id: InputDevice.DefaultDeviceId,
|
||||||
volume: 100,
|
volume: 100,
|
||||||
|
|
||||||
vad_threshold: {
|
vad_threshold: {
|
||||||
|
@ -145,7 +141,7 @@ export class RecorderProfile {
|
||||||
Object.assign(this.config, config || {});
|
Object.assign(this.config, config || {});
|
||||||
}
|
}
|
||||||
|
|
||||||
aplayer.on_ready(async () => {
|
getAudioBackend().executeWhenInitialized(async () => {
|
||||||
await getRecorderBackend().getDeviceList().awaitInitialized();
|
await getRecorderBackend().getDeviceList().awaitInitialized();
|
||||||
|
|
||||||
await this.initializeInput();
|
await this.initializeInput();
|
||||||
|
@ -185,7 +181,7 @@ export class RecorderProfile {
|
||||||
if(this.config.device_id) {
|
if(this.config.device_id) {
|
||||||
await this.input.setDeviceId(this.config.device_id);
|
await this.input.setDeviceId(this.config.device_id);
|
||||||
} else {
|
} else {
|
||||||
await this.input.setDeviceId(IDevice.DefaultDeviceId);
|
await this.input.setDeviceId(InputDevice.DefaultDeviceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,15 +197,12 @@ export class RecorderProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.pptHookRegistered) {
|
if(this.pptHookRegistered) {
|
||||||
ppt.unregister_key_hook(this.pptHook);
|
getKeyBoard().unregisterHook(this.pptHook);
|
||||||
this.pptHookRegistered = false;
|
this.pptHookRegistered = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(const key of ["key_alt", "key_ctrl", "key_shift", "key_windows", "key_code"]) {
|
Object.assign(this.pptHook, this.getPushToTalkKey());
|
||||||
this.pptHook[key] = this.config.vad_push_to_talk[key];
|
getKeyBoard().registerHook(this.pptHook);
|
||||||
}
|
|
||||||
|
|
||||||
ppt.register_key_hook(this.pptHook);
|
|
||||||
this.pptHookRegistered = true;
|
this.pptHookRegistered = true;
|
||||||
|
|
||||||
this.registeredFilter["ppt-gate"]?.setState(true);
|
this.registeredFilter["ppt-gate"]?.setState(true);
|
||||||
|
@ -227,7 +220,7 @@ export class RecorderProfile {
|
||||||
this.registeredFilter["ppt-gate"].setEnabled(false);
|
this.registeredFilter["ppt-gate"].setEnabled(false);
|
||||||
|
|
||||||
if(this.pptHookRegistered) {
|
if(this.pptHookRegistered) {
|
||||||
ppt.unregister_key_hook(this.pptHook);
|
getKeyBoard().unregisterHook(this.pptHook);
|
||||||
this.pptHookRegistered = false;
|
this.pptHookRegistered = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,10 +244,8 @@ export class RecorderProfile {
|
||||||
filter.setEnabled(true);
|
filter.setEnabled(true);
|
||||||
filter.setState(true); /* by default set filtered */
|
filter.setState(true); /* by default set filtered */
|
||||||
|
|
||||||
for(const key of ["key_alt", "key_ctrl", "key_shift", "key_windows", "key_code"])
|
Object.assign(this.pptHook, this.getPushToTalkKey());
|
||||||
this.pptHook[key] = this.config.vad_push_to_talk[key];
|
getKeyBoard().registerHook(this.pptHook);
|
||||||
|
|
||||||
ppt.register_key_hook(this.pptHook);
|
|
||||||
this.pptHookRegistered = true;
|
this.pptHookRegistered = true;
|
||||||
} else if(this.config.vad_type === "active") {
|
} else if(this.config.vad_type === "active") {
|
||||||
/* we don't have to initialize any filters */
|
/* we don't have to initialize any filters */
|
||||||
|
@ -311,10 +302,27 @@ export class RecorderProfile {
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
getPushToTalkKey() : KeyDescriptor { return this.config.vad_push_to_talk; }
|
getPushToTalkKey() : KeyDescriptor {
|
||||||
|
return {
|
||||||
|
keyCode: this.config.vad_push_to_talk.key_code,
|
||||||
|
|
||||||
|
keyAlt: this.config.vad_push_to_talk.key_alt,
|
||||||
|
keyCtrl: this.config.vad_push_to_talk.key_ctrl,
|
||||||
|
keyShift: this.config.vad_push_to_talk.key_shift,
|
||||||
|
keyWindows: this.config.vad_push_to_talk.key_windows,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setPushToTalkKey(key: KeyDescriptor) {
|
setPushToTalkKey(key: KeyDescriptor) {
|
||||||
for(const _key of ["key_alt", "key_ctrl", "key_shift", "key_windows", "key_code"])
|
this.config.vad_push_to_talk = {
|
||||||
this.config.vad_push_to_talk[_key] = key[_key];
|
delay: this.config.vad_push_to_talk.delay,
|
||||||
|
key_code: key.keyCode,
|
||||||
|
|
||||||
|
key_alt: key.keyAlt,
|
||||||
|
key_ctrl: key.keyCtrl,
|
||||||
|
key_shift: key.keyShift,
|
||||||
|
key_windows: key.keyWindows
|
||||||
|
};
|
||||||
|
|
||||||
this.reinitializePPTHook();
|
this.reinitializePPTHook();
|
||||||
this.save();
|
this.save();
|
||||||
|
@ -329,8 +337,8 @@ export class RecorderProfile {
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
getDeviceId() : string | typeof IDevice.DefaultDeviceId | typeof IDevice.NoDeviceId { return this.config.device_id; }
|
getDeviceId() : string | typeof InputDevice.DefaultDeviceId | typeof InputDevice.NoDeviceId { return this.config.device_id; }
|
||||||
setDevice(device: IDevice | typeof IDevice.DefaultDeviceId | typeof IDevice.NoDeviceId) : Promise<void> {
|
setDevice(device: InputDevice | typeof InputDevice.DefaultDeviceId | typeof InputDevice.NoDeviceId) : Promise<void> {
|
||||||
let deviceId;
|
let deviceId;
|
||||||
if(typeof device === "object") {
|
if(typeof device === "object") {
|
||||||
deviceId = device.deviceId;
|
deviceId = device.deviceId;
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
"baseUrl": "../../",
|
"baseUrl": "../../",
|
||||||
"paths": {
|
"paths": {
|
||||||
"tc-shared/*": ["shared/js/*"],
|
"tc-shared/*": ["shared/js/*"],
|
||||||
"tc-backend/*": ["shared/backend.d/*"],
|
|
||||||
"tc-loader": ["loader/exports/loader.d.ts"],
|
"tc-loader": ["loader/exports/loader.d.ts"],
|
||||||
"svg-sprites/*": ["shared/svg-sprites/*"],
|
"svg-sprites/*": ["shared/svg-sprites/*"],
|
||||||
"vendor/xbbcode/*": ["vendor/xbbcode/src/*"],
|
"vendor/xbbcode/*": ["vendor/xbbcode/src/*"],
|
||||||
|
|
|
@ -12,9 +12,6 @@
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"tc-shared/*": ["shared/js/*"],
|
"tc-shared/*": ["shared/js/*"],
|
||||||
"tc-backend/audio-lib/*": ["web/audio-lib/pkg/*"], /* specific web part */
|
|
||||||
"tc-backend/web/*": ["web/app/*"], /* specific web part */
|
|
||||||
"tc-backend/*": ["shared/backend.d/*"],
|
|
||||||
"tc-loader": ["loader/exports/loader.d.ts"],
|
"tc-loader": ["loader/exports/loader.d.ts"],
|
||||||
"tc-events": ["vendor/TeaEventBus/src/index.ts"],
|
"tc-events": ["vendor/TeaEventBus/src/index.ts"],
|
||||||
"tc-services": ["vendor/TeaClientServices/src/index.ts"],
|
"tc-services": ["vendor/TeaClientServices/src/index.ts"],
|
||||||
|
|
|
@ -12,7 +12,6 @@ import {
|
||||||
TransferSourceType,
|
TransferSourceType,
|
||||||
TransferTargetType
|
TransferTargetType
|
||||||
} from "tc-shared/file/Transfer";
|
} from "tc-shared/file/Transfer";
|
||||||
import * as log from "tc-shared/log";
|
|
||||||
import {LogCategory, logError} from "tc-shared/log";
|
import {LogCategory, logError} from "tc-shared/log";
|
||||||
import { tr } from "tc-shared/i18n/localize";
|
import { tr } from "tc-shared/i18n/localize";
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
import {AbstractKeyBoard, EventType, KeyEvent} from "tc-shared/PPTListener";
|
||||||
|
|
||||||
|
export class WebKeyBoard extends AbstractKeyBoard {
|
||||||
|
private readonly listenerBlur;
|
||||||
|
private readonly listenerKeyPress;
|
||||||
|
private readonly listenerKeyDown;
|
||||||
|
private readonly listenerKeyUp;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.listenerBlur = () => this.handleBlurEvent();
|
||||||
|
this.listenerKeyPress = event => this.handleNativeKeyEvent(EventType.KEY_TYPED, event);
|
||||||
|
this.listenerKeyDown = event => this.handleNativeKeyEvent(EventType.KEY_PRESS, event);
|
||||||
|
this.listenerKeyUp = event => this.handleNativeKeyEvent(EventType.KEY_RELEASE, event);
|
||||||
|
|
||||||
|
window.addEventListener("blur", () => this.handleBlurEvent());
|
||||||
|
document.addEventListener('keypress', this.listenerKeyPress);
|
||||||
|
document.addEventListener('keydown', this.listenerKeyDown);
|
||||||
|
document.addEventListener('keyup', this.listenerKeyUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
window.removeEventListener("blur", () => this.handleBlurEvent());
|
||||||
|
document.removeEventListener('keypress', this.listenerKeyPress);
|
||||||
|
document.removeEventListener('keydown', this.listenerKeyDown);
|
||||||
|
document.removeEventListener('keyup', this.listenerKeyUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleNativeKeyEvent(type: EventType, nativeEvent: KeyboardEvent) {
|
||||||
|
const event: KeyEvent = {
|
||||||
|
type: type,
|
||||||
|
|
||||||
|
key: nativeEvent.key,
|
||||||
|
keyCode: nativeEvent.code,
|
||||||
|
|
||||||
|
keyCtrl: nativeEvent.ctrlKey,
|
||||||
|
keyShift: nativeEvent.shiftKey,
|
||||||
|
keyAlt: nativeEvent.altKey,
|
||||||
|
keyWindows: nativeEvent.metaKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.fireKeyEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleBlurEvent() {
|
||||||
|
this.resetKeyboardState();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
import {AudioBackend, AudioBackendEvents, OutputDevice} from "tc-shared/audio/Player";
|
||||||
|
import {LogCategory, logDebug, logError, logInfo, logWarn} from "tc-shared/log";
|
||||||
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
|
import {Registry} from "tc-events";
|
||||||
|
|
||||||
|
const kWebDevice: OutputDevice = {
|
||||||
|
device_id: "default",
|
||||||
|
name: "default playback",
|
||||||
|
driver: 'Web Audio'
|
||||||
|
};
|
||||||
|
|
||||||
|
export class WebAudioBackend implements AudioBackend {
|
||||||
|
private readonly events: Registry<AudioBackendEvents>;
|
||||||
|
private readonly audioContext: AudioContext;
|
||||||
|
private state: "initializing" | "running" | "closed";
|
||||||
|
private masterVolume: number;
|
||||||
|
|
||||||
|
private gestureListener: () => void;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.events = new Registry<AudioBackendEvents>();
|
||||||
|
this.state = "initializing";
|
||||||
|
this.masterVolume = 1;
|
||||||
|
|
||||||
|
this.audioContext = new (window.webkitAudioContext || window.AudioContext)();
|
||||||
|
this.audioContext.onstatechange = () => this.handleAudioContextStateChanged(false);
|
||||||
|
this.handleAudioContextStateChanged(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.state = "closed";
|
||||||
|
|
||||||
|
document.removeEventListener("click", this.gestureListener);
|
||||||
|
this.gestureListener = undefined;
|
||||||
|
|
||||||
|
this.audioContext.close().catch(error => {
|
||||||
|
logWarn(LogCategory.AUDIO, tr("Failed to close AudioContext: %o"), error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
executeWhenInitialized(callback: () => void) {
|
||||||
|
if(this.state === "running") {
|
||||||
|
callback();
|
||||||
|
} else {
|
||||||
|
this.events.one("notify_initialized", callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isInitialized(): boolean {
|
||||||
|
return this.state === "running";
|
||||||
|
}
|
||||||
|
|
||||||
|
getAudioContext(): AudioContext | undefined {
|
||||||
|
return this.audioContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAvailableDevices(): Promise<OutputDevice[]> {
|
||||||
|
return [ kWebDevice ];
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultDeviceId(): string {
|
||||||
|
return kWebDevice.device_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMasterVolume(): number {
|
||||||
|
return this.masterVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMasterVolume(volume: number) {
|
||||||
|
if(this.masterVolume === volume) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldVolume = this.masterVolume;
|
||||||
|
this.masterVolume = volume;
|
||||||
|
this.events.fire("notify_volume_changed", {
|
||||||
|
oldVolume: oldVolume,
|
||||||
|
newVolume: volume
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isDeviceRefreshAvailable(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshDevices(): Promise<void> {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleAudioContextStateChanged(initialState: boolean) {
|
||||||
|
switch (this.audioContext.state) {
|
||||||
|
case "suspended":
|
||||||
|
if(initialState) {
|
||||||
|
logDebug(LogCategory.AUDIO, tr("Created new AudioContext but user hasn't yet allowed audio playback. Awaiting his gesture."));
|
||||||
|
this.awaitGesture();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
logWarn(LogCategory.AUDIO, tr("AudioContext state changed to 'suspended'. Trying to resume it."));
|
||||||
|
this.tryResume();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "closed":
|
||||||
|
if(this.state === "closed") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logError(LogCategory.AUDIO, tr("AudioContext state changed to 'closed'. No audio will be payed."));
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "running":
|
||||||
|
logDebug(LogCategory.AUDIO, tr("Successfully initialized the AudioContext."));
|
||||||
|
this.state = "running";
|
||||||
|
this.events.fire("notify_initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private tryResume() {
|
||||||
|
this.audioContext.resume().then(() => {
|
||||||
|
logInfo(LogCategory.AUDIO, tr("Successfully resumed AudioContext."));
|
||||||
|
}).catch(error => {
|
||||||
|
logError(LogCategory.AUDIO, tr("Failed to resume AudioContext: %o"), error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private awaitGesture() {
|
||||||
|
if(this.gestureListener) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.gestureListener = () => {
|
||||||
|
document.removeEventListener("click", this.gestureListener);
|
||||||
|
this.gestureListener = undefined;
|
||||||
|
this.tryResume();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("click", this.gestureListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setCurrentDevice(targetId: string | undefined): Promise<void> {
|
||||||
|
/* TODO: Mute on "no device"? */
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentDevice(): OutputDevice {
|
||||||
|
return kWebDevice;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import {AudioRecorderBacked, DeviceList, IDevice,} from "tc-shared/audio/recorder";
|
import {AudioRecorderBacked, DeviceList, InputDevice,} from "tc-shared/audio/Recorder";
|
||||||
import {Registry} from "tc-shared/events";
|
import {Registry} from "tc-shared/events";
|
||||||
import {
|
import {
|
||||||
AbstractInput,
|
AbstractInput,
|
||||||
|
@ -12,12 +12,12 @@ import {
|
||||||
NodeInputConsumer
|
NodeInputConsumer
|
||||||
} from "tc-shared/voice/RecorderBase";
|
} from "tc-shared/voice/RecorderBase";
|
||||||
import {LogCategory, logDebug, logWarn} from "tc-shared/log";
|
import {LogCategory, logDebug, logWarn} from "tc-shared/log";
|
||||||
import * as aplayer from "./player";
|
|
||||||
import {JAbstractFilter, JStateFilter, JThresholdFilter} from "./RecorderFilter";
|
import {JAbstractFilter, JStateFilter, JThresholdFilter} from "./RecorderFilter";
|
||||||
import {Filter, FilterType, FilterTypeClass} from "tc-shared/voice/Filter";
|
import {Filter, FilterType, FilterTypeClass} from "tc-shared/voice/Filter";
|
||||||
import {inputDeviceList} from "tc-backend/web/audio/RecorderDeviceList";
|
import {inputDeviceList} from "./RecorderDeviceList";
|
||||||
import {requestMediaStream, stopMediaStream} from "tc-shared/media/Stream";
|
import {requestMediaStream, stopMediaStream} from "tc-shared/media/Stream";
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
|
import {getAudioBackend} from "tc-shared/audio/Player";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface MediaStream {
|
interface MediaStream {
|
||||||
|
@ -25,7 +25,7 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebIDevice extends IDevice {
|
export interface WebIDevice extends InputDevice {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ export class WebAudioRecorder implements AudioRecorderBacked {
|
||||||
return new JavascriptInput();
|
return new JavascriptInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
async createLevelMeter(device: IDevice): Promise<LevelMeter> {
|
async createLevelMeter(device: InputDevice): Promise<LevelMeter> {
|
||||||
const meter = new JavascriptLevelMeter(device as any);
|
const meter = new JavascriptLevelMeter(device as any);
|
||||||
await meter.initialize();
|
await meter.initialize();
|
||||||
return meter;
|
return meter;
|
||||||
|
@ -81,14 +81,14 @@ class JavascriptInput implements AbstractInput {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.events = new Registry<InputEvents>();
|
this.events = new Registry<InputEvents>();
|
||||||
|
|
||||||
aplayer.on_ready(() => this.handleAudioInitialized());
|
getAudioBackend().executeWhenInitialized(() => this.handleAudioInitialized());
|
||||||
this.audioScriptProcessorCallback = this.handleAudio.bind(this);
|
this.audioScriptProcessorCallback = this.handleAudio.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() { }
|
destroy() { }
|
||||||
|
|
||||||
private handleAudioInitialized() {
|
private handleAudioInitialized() {
|
||||||
this.audioContext = aplayer.context();
|
this.audioContext = getAudioBackend().getAudioContext();
|
||||||
this.audioNodeMute = this.audioContext.createGain();
|
this.audioNodeMute = this.audioContext.createGain();
|
||||||
this.audioNodeMute.gain.value = 0;
|
this.audioNodeMute.gain.value = 0;
|
||||||
this.audioNodeMute.connect(this.audioContext.destination);
|
this.audioNodeMute.connect(this.audioContext.destination);
|
||||||
|
@ -179,7 +179,7 @@ class JavascriptInput implements AbstractInput {
|
||||||
|
|
||||||
private async doStart() : Promise<InputStartError | true> {
|
private async doStart() : Promise<InputStartError | true> {
|
||||||
try {
|
try {
|
||||||
if(!aplayer.initialized() || !this.audioContext) {
|
if(!getAudioBackend().isInitialized() || !this.audioContext) {
|
||||||
return InputStartError.ESYSTEMUNINITIALIZED;
|
return InputStartError.ESYSTEMUNINITIALIZED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,9 +189,9 @@ class JavascriptInput implements AbstractInput {
|
||||||
this.setState(InputState.INITIALIZING);
|
this.setState(InputState.INITIALIZING);
|
||||||
|
|
||||||
let deviceId;
|
let deviceId;
|
||||||
if(this.deviceId === IDevice.NoDeviceId) {
|
if(this.deviceId === InputDevice.NoDeviceId) {
|
||||||
throw tr("no device selected");
|
throw tr("no device selected");
|
||||||
} else if(this.deviceId === IDevice.DefaultDeviceId) {
|
} else if(this.deviceId === InputDevice.DefaultDeviceId) {
|
||||||
deviceId = undefined;
|
deviceId = undefined;
|
||||||
} else {
|
} else {
|
||||||
deviceId = this.deviceId;
|
deviceId = this.deviceId;
|
||||||
|
@ -511,7 +511,7 @@ class JavascriptLevelMeter implements LevelMeter {
|
||||||
try {
|
try {
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
const timeout = setTimeout(reject, 5000);
|
const timeout = setTimeout(reject, 5000);
|
||||||
aplayer.on_ready(() => {
|
getAudioBackend().executeWhenInitialized(() => {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
@ -519,7 +519,7 @@ class JavascriptLevelMeter implements LevelMeter {
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
throw tr("audio context timeout");
|
throw tr("audio context timeout");
|
||||||
}
|
}
|
||||||
this._context = aplayer.context();
|
this._context = getAudioBackend().getAudioContext();
|
||||||
if(!this._context) throw tr("invalid context");
|
if(!this._context) throw tr("invalid context");
|
||||||
|
|
||||||
this._gain_node = this._context.createGain();
|
this._gain_node = this._context.createGain();
|
||||||
|
@ -591,7 +591,7 @@ class JavascriptLevelMeter implements LevelMeter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getDevice(): IDevice {
|
getDevice(): InputDevice {
|
||||||
return this._device;
|
return this._device;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,12 @@ import {
|
||||||
AbstractDeviceList,
|
AbstractDeviceList,
|
||||||
DeviceListEvents,
|
DeviceListEvents,
|
||||||
DeviceListState,
|
DeviceListState,
|
||||||
IDevice,
|
InputDevice,
|
||||||
PermissionState
|
PermissionState
|
||||||
} from "tc-shared/audio/recorder";
|
} from "tc-shared/audio/Recorder";
|
||||||
import * as log from "tc-shared/log";
|
|
||||||
import {LogCategory, logDebug, logError} from "tc-shared/log";
|
import {LogCategory, logDebug, logError} from "tc-shared/log";
|
||||||
import {Registry} from "tc-shared/events";
|
import {Registry} from "tc-shared/events";
|
||||||
import {WebIDevice} from "tc-backend/web/audio/Recorder";
|
import {WebIDevice} from "./Recorder";
|
||||||
import * as loader from "tc-loader";
|
import * as loader from "tc-loader";
|
||||||
import {queryMediaPermissions} from "tc-shared/media/Stream";
|
import {queryMediaPermissions} from "tc-shared/media/Stream";
|
||||||
import { tr } from "tc-shared/i18n/localize";
|
import { tr } from "tc-shared/i18n/localize";
|
||||||
|
@ -49,7 +48,7 @@ class WebInputDeviceList extends AbstractDeviceList {
|
||||||
return "default";
|
return "default";
|
||||||
}
|
}
|
||||||
|
|
||||||
getDevices(): IDevice[] {
|
getDevices(): InputDevice[] {
|
||||||
return this.devices;
|
return this.devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {LogCategory, logError, logWarn} from "tc-shared/log";
|
import {LogCategory, logError, logWarn} from "tc-shared/log";
|
||||||
import {SoundFile} from "tc-shared/sound/Sounds";
|
|
||||||
import * as aplayer from "./player";
|
|
||||||
import { tr } from "tc-shared/i18n/localize";
|
import { tr } from "tc-shared/i18n/localize";
|
||||||
|
import {getAudioBackend} from "tc-shared/audio/Player";
|
||||||
|
import {SoundBackend, SoundFile} from "tc-shared/audio/Sounds";
|
||||||
|
|
||||||
interface SoundEntry {
|
interface SoundEntry {
|
||||||
cached?: AudioBuffer;
|
cached?: AudioBuffer;
|
||||||
|
@ -13,7 +13,7 @@ const error_already_handled = "---- error handled ---";
|
||||||
const file_cache: {[key: string]: Promise<SoundEntry> & { timestamp: number }} = {};
|
const file_cache: {[key: string]: Promise<SoundEntry> & { timestamp: number }} = {};
|
||||||
let warned = false;
|
let warned = false;
|
||||||
|
|
||||||
function get_song_entry(file: SoundFile) : Promise<SoundEntry> {
|
function getSongEntry(file: SoundFile) : Promise<SoundEntry> {
|
||||||
if(typeof file_cache[file.path] === "object") {
|
if(typeof file_cache[file.path] === "object") {
|
||||||
return new Promise<SoundEntry>((resolve, reject) => {
|
return new Promise<SoundEntry>((resolve, reject) => {
|
||||||
if(file_cache[file.path].timestamp + 60 * 1000 > Date.now()) {
|
if(file_cache[file.path].timestamp + 60 * 1000 > Date.now()) {
|
||||||
|
@ -26,12 +26,12 @@ function get_song_entry(file: SoundFile) : Promise<SoundEntry> {
|
||||||
if(file_cache[file.path].timestamp + 60 * 1000 > original_timestamp)
|
if(file_cache[file.path].timestamp + 60 * 1000 > original_timestamp)
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
delete file_cache[file.path];
|
delete file_cache[file.path];
|
||||||
return get_song_entry(file);
|
return getSongEntry(file);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = aplayer.context();
|
const context = getAudioBackend().getAudioContext();
|
||||||
if(!context) throw tr("audio context not initialized");
|
if(!context) throw tr("audio context not initialized");
|
||||||
|
|
||||||
return (file_cache[file.path] = Object.assign((async () => {
|
return (file_cache[file.path] = Object.assign((async () => {
|
||||||
|
@ -78,8 +78,8 @@ function get_song_entry(file: SoundFile) : Promise<SoundEntry> {
|
||||||
})(), { timestamp: Date.now() }));
|
})(), { timestamp: Date.now() }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function play_sound(file: SoundFile) : Promise<void> {
|
async function replaySound(file: SoundFile) : Promise<void> {
|
||||||
const entry = get_song_entry(file);
|
const entry = getSongEntry(file);
|
||||||
if(!entry) {
|
if(!entry) {
|
||||||
logWarn(LogCategory.AUDIO, tr("Failed to replay sound %s because it could not be resolved."), file.path);
|
logWarn(LogCategory.AUDIO, tr("Failed to replay sound %s because it could not be resolved."), file.path);
|
||||||
return;
|
return;
|
||||||
|
@ -89,7 +89,7 @@ export async function play_sound(file: SoundFile) : Promise<void> {
|
||||||
const sound = await entry;
|
const sound = await entry;
|
||||||
|
|
||||||
if(sound.cached) {
|
if(sound.cached) {
|
||||||
const context = aplayer.context();
|
const context = getAudioBackend().getAudioContext();
|
||||||
if(!context) throw tr("audio context not initialized (this error should never show up!)");
|
if(!context) throw tr("audio context not initialized (this error should never show up!)");
|
||||||
|
|
||||||
const player = context.createBufferSource();
|
const player = context.createBufferSource();
|
||||||
|
@ -127,3 +127,10 @@ export async function play_sound(file: SoundFile) : Promise<void> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class WebSoundBackend implements SoundBackend {
|
||||||
|
playSound(sound: SoundFile): Promise<void> {
|
||||||
|
return replaySound(sound);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,125 +0,0 @@
|
||||||
import {Device} from "tc-shared/audio/player";
|
|
||||||
import * as log from "tc-shared/log";
|
|
||||||
import {LogCategory, logError, logInfo} from "tc-shared/log";
|
|
||||||
import { tr } from "tc-shared/i18n/localize";
|
|
||||||
|
|
||||||
/* lets try without any gestures, maybe the user already clicked the page */
|
|
||||||
const kAvoidAudioContextWarning = false;
|
|
||||||
|
|
||||||
let audioContextRequiredGesture = false;
|
|
||||||
let audioContextInstance: AudioContext;
|
|
||||||
let globalAudioGainInstance: GainNode;
|
|
||||||
|
|
||||||
let audioContextInitializeCallbacks: (() => any)[] = [];
|
|
||||||
let _master_volume: number = 1;
|
|
||||||
let _no_device = false;
|
|
||||||
|
|
||||||
export function initialize() : boolean {
|
|
||||||
context();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initialized() : boolean {
|
|
||||||
return !!audioContextInstance && audioContextInstance.state === 'running';
|
|
||||||
}
|
|
||||||
|
|
||||||
function fire_initialized() {
|
|
||||||
logInfo(LogCategory.AUDIO, tr("Fire audio player initialized for %d listeners"), audioContextInitializeCallbacks.length);
|
|
||||||
while(audioContextInitializeCallbacks.length > 0)
|
|
||||||
audioContextInitializeCallbacks.pop_front()();
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNewContext() {
|
|
||||||
audioContextInstance = new (window.webkitAudioContext || window.AudioContext)();
|
|
||||||
audioContextInstance.onstatechange = () => {
|
|
||||||
if(audioContextInstance.state === "running")
|
|
||||||
fire_initialized();
|
|
||||||
};
|
|
||||||
|
|
||||||
audioContextInitializeCallbacks.unshift(() => {
|
|
||||||
globalAudioGainInstance = audioContextInstance.createGain();
|
|
||||||
globalAudioGainInstance.gain.value = _no_device ? 0 : _master_volume;
|
|
||||||
globalAudioGainInstance.connect(audioContextInstance.destination);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(audioContextInstance.state === "suspended") {
|
|
||||||
audioContextRequiredGesture = true;
|
|
||||||
return audioContextInstance;
|
|
||||||
} else if(audioContextInstance.state === "running") {
|
|
||||||
fire_initialized();
|
|
||||||
} else if(audioContextInstance.state === "closed") {
|
|
||||||
throw tr("Audio context has been closed");
|
|
||||||
} else {
|
|
||||||
throw tr("invalid audio context state");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function context() : AudioContext {
|
|
||||||
if(audioContextInstance || kAvoidAudioContextWarning)
|
|
||||||
return audioContextInstance;
|
|
||||||
|
|
||||||
if(!audioContextInstance)
|
|
||||||
createNewContext();
|
|
||||||
|
|
||||||
return audioContextInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function get_master_volume() : number {
|
|
||||||
return _master_volume;
|
|
||||||
}
|
|
||||||
export function set_master_volume(volume: number) {
|
|
||||||
_master_volume = volume;
|
|
||||||
if(globalAudioGainInstance)
|
|
||||||
globalAudioGainInstance.gain.value = _no_device ? 0 : _master_volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function destination() : AudioNode {
|
|
||||||
const ctx = context();
|
|
||||||
if(!ctx) throw tr("Audio player isn't initialized yet!");
|
|
||||||
|
|
||||||
return globalAudioGainInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function on_ready(cb: () => any) {
|
|
||||||
if(initialized())
|
|
||||||
cb();
|
|
||||||
else
|
|
||||||
audioContextInitializeCallbacks.push(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const WEB_DEVICE: Device = {
|
|
||||||
device_id: "default",
|
|
||||||
name: "default playback",
|
|
||||||
driver: 'Web Audio'
|
|
||||||
};
|
|
||||||
|
|
||||||
export function available_devices() : Promise<Device[]> {
|
|
||||||
return Promise.resolve([WEB_DEVICE])
|
|
||||||
}
|
|
||||||
|
|
||||||
export function set_device(device_id: string) : Promise<void> {
|
|
||||||
_no_device = !device_id;
|
|
||||||
globalAudioGainInstance.gain.value = _no_device ? 0 : _master_volume;
|
|
||||||
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function current_device() : Device {
|
|
||||||
return WEB_DEVICE;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initializeFromGesture() {
|
|
||||||
if(audioContextInstance) {
|
|
||||||
if(audioContextInstance.state !== "running") {
|
|
||||||
audioContextInstance.resume().catch(error => {
|
|
||||||
logError(LogCategory.AUDIO, tr("Failed to initialize audio context instance from gesture: %o"), error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
createNewContext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function globalAudioContext() : AudioContext {
|
|
||||||
return context();
|
|
||||||
}
|
|
|
@ -10,8 +10,9 @@ function unescapeCommandValue(value: string) : string {
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
index = value.indexOf('\\', lastIndex);
|
index = value.indexOf('\\', lastIndex);
|
||||||
if(index === -1 || index >= value.length + 1)
|
if(index === -1 || index >= value.length + 1) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
let replace;
|
let replace;
|
||||||
switch (value.charAt(index + 1)) {
|
switch (value.charAt(index + 1)) {
|
||||||
|
@ -58,8 +59,9 @@ export function parseCommand(command: string): ParsedCommand {
|
||||||
const parts = command.split("|").map(element => element.split(" ").map(e => e.trim()).filter(e => !!e));
|
const parts = command.split("|").map(element => element.split(" ").map(e => e.trim()).filter(e => !!e));
|
||||||
|
|
||||||
let cmd;
|
let cmd;
|
||||||
if(parts[0][0].indexOf("=") === -1)
|
if(parts[0][0].indexOf("=") === -1) {
|
||||||
cmd = parts[0].pop_front();
|
cmd = parts[0].pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
let switches = [];
|
let switches = [];
|
||||||
let payloads = [];
|
let payloads = [];
|
||||||
|
@ -72,11 +74,12 @@ export function parseCommand(command: string): ParsedCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
const separator = keyValue.indexOf('=');
|
const separator = keyValue.indexOf('=');
|
||||||
if(separator === -1)
|
if(separator === -1) {
|
||||||
payload[keyValue] = "";
|
payload[keyValue] = "";
|
||||||
else
|
} else {
|
||||||
payload[keyValue.substring(0, separator)] = unescapeCommandValue(keyValue.substring(separator + 1));
|
payload[keyValue.substring(0, separator)] = unescapeCommandValue(keyValue.substring(separator + 1));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
payloads.push(payload)
|
payloads.push(payload)
|
||||||
});
|
});
|
||||||
|
@ -95,13 +98,15 @@ export function buildCommand(data: any | any[], switches?: string[], command?: s
|
||||||
result += " |";
|
result += " |";
|
||||||
for(const key of Object.keys(payload)) {
|
for(const key of Object.keys(payload)) {
|
||||||
result += " " + key;
|
result += " " + key;
|
||||||
if(payload[key] !== undefined && payload[key] !== null)
|
if(payload[key] !== undefined && payload[key] !== null) {
|
||||||
result += " " + key + "=" + escapeCommandValue(payload[key].toString());
|
result += " " + key + "=" + escapeCommandValue(payload[key].toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(switches?.length)
|
if(switches?.length) {
|
||||||
result += " " + switches.map(e => "-" + e).join(" ");
|
result += " " + switches.map(e => "-" + e).join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
return command ? command + result.substring(2) : result.substring(3);
|
return command ? command + result.substring(2) : result.substring(3);
|
||||||
}
|
}
|
|
@ -13,11 +13,11 @@ import * as log from "tc-shared/log";
|
||||||
import {LogCategory, logDebug, logError, logInfo, logTrace, logWarn} from "tc-shared/log";
|
import {LogCategory, logDebug, logError, logInfo, logTrace, logWarn} from "tc-shared/log";
|
||||||
import {Regex} from "tc-shared/ui/modal/ModalConnect";
|
import {Regex} from "tc-shared/ui/modal/ModalConnect";
|
||||||
import {AbstractCommandHandlerBoss} from "tc-shared/connection/AbstractCommandHandler";
|
import {AbstractCommandHandlerBoss} from "tc-shared/connection/AbstractCommandHandler";
|
||||||
import {WrappedWebSocket} from "tc-backend/web/connection/WrappedWebSocket";
|
import {WrappedWebSocket} from "./WrappedWebSocket";
|
||||||
import {AbstractVoiceConnection} from "tc-shared/connection/VoiceConnection";
|
import {AbstractVoiceConnection} from "tc-shared/connection/VoiceConnection";
|
||||||
import {parseCommand} from "tc-backend/web/connection/CommandParser";
|
import {parseCommand} from "./CommandParser";
|
||||||
import {ServerAddress} from "tc-shared/tree/Server";
|
import {ServerAddress} from "tc-shared/tree/Server";
|
||||||
import {RtpVoiceConnection} from "tc-backend/web/voice/Connection";
|
import {RtpVoiceConnection} from "../voice/Connection";
|
||||||
import {VideoConnection} from "tc-shared/connection/VideoConnection";
|
import {VideoConnection} from "tc-shared/connection/VideoConnection";
|
||||||
import {ServerFeature} from "tc-shared/connection/ServerFeatures";
|
import {ServerFeature} from "tc-shared/connection/ServerFeatures";
|
||||||
import {RTCConnection} from "tc-shared/connection/rtc/Connection";
|
import {RTCConnection} from "tc-shared/connection/rtc/Connection";
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
import * as log from "tc-shared/log";
|
|
||||||
import {LogCategory, logError, logTrace} from "tc-shared/log";
|
import {LogCategory, logError, logTrace} from "tc-shared/log";
|
||||||
|
|
||||||
export enum RRType {
|
export enum RRType {
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import * as loader from "tc-loader";
|
||||||
|
import {Stage} from "tc-loader";
|
||||||
|
import {setAudioBackend} from "tc-shared/audio/Player";
|
||||||
|
import {WebAudioBackend} from "../audio/Player";
|
||||||
|
|
||||||
|
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
|
name: "audio backend init",
|
||||||
|
function: async () => {
|
||||||
|
setAudioBackend(new WebAudioBackend());
|
||||||
|
},
|
||||||
|
priority: 100
|
||||||
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
import {setRecorderBackend} from "tc-shared/audio/recorder";
|
import {setRecorderBackend} from "tc-shared/audio/Recorder";
|
||||||
import {WebAudioRecorder} from "../audio/Recorder";
|
import {WebAudioRecorder} from "../audio/Recorder";
|
||||||
|
|
||||||
setRecorderBackend(new WebAudioRecorder());
|
setRecorderBackend(new WebAudioRecorder());
|
|
@ -1,7 +1,7 @@
|
||||||
import {DNSAddress, DNSProvider, DNSResolveOptions, DNSResolveResult, setDNSProvider} from "tc-shared/dns";
|
import {DNSAddress, DNSProvider, DNSResolveOptions, DNSResolveResult, setDNSProvider} from "tc-shared/dns";
|
||||||
import {resolveAddressIpV4, resolveTeaSpeakServerAddress} from "tc-backend/web/dns/resolver";
|
|
||||||
import {LogCategory, logError} from "tc-shared/log";
|
import {LogCategory, logError} from "tc-shared/log";
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
|
import {resolveAddressIpV4, resolveTeaSpeakServerAddress} from "../dns/resolver";
|
||||||
|
|
||||||
setDNSProvider(new class implements DNSProvider {
|
setDNSProvider(new class implements DNSProvider {
|
||||||
resolveAddress(address: DNSAddress, options: DNSResolveOptions): Promise<DNSResolveResult> {
|
resolveAddress(address: DNSAddress, options: DNSResolveOptions): Promise<DNSResolveResult> {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as loader from "tc-loader";
|
import * as loader from "tc-loader";
|
||||||
import {Stage} from "tc-loader";
|
import {Stage} from "tc-loader";
|
||||||
import {setExternalModalControllerFactory} from "tc-shared/ui/react-elements/external-modal";
|
import {setExternalModalControllerFactory} from "tc-shared/ui/react-elements/external-modal";
|
||||||
import {ExternalModalController} from "tc-backend/web/ExternalModalFactory";
|
import {ExternalModalController} from "../ExternalModalFactory";
|
||||||
|
|
||||||
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
|
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
priority: 50,
|
priority: 50,
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import * as loader from "tc-loader";
|
||||||
|
import {Stage} from "tc-loader";
|
||||||
|
import {setKeyBoardBackend} from "tc-shared/PPTListener";
|
||||||
|
import {WebKeyBoard} from "../KeyBoard";
|
||||||
|
|
||||||
|
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
|
name: "audio backend init",
|
||||||
|
function: async () => setKeyBoardBackend(new WebKeyBoard()),
|
||||||
|
priority: 100
|
||||||
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
import {setMenuBarDriver} from "tc-shared/ui/frames/menu-bar";
|
import {setMenuBarDriver} from "tc-shared/ui/frames/menu-bar";
|
||||||
import {WebMenuBarDriver} from "tc-backend/web/ui/menu-bar/Controller";
|
import {WebMenuBarDriver} from "../ui/menu-bar/Controller";
|
||||||
|
|
||||||
setMenuBarDriver(new WebMenuBarDriver());
|
setMenuBarDriver(new WebMenuBarDriver());
|
|
@ -1,9 +1,9 @@
|
||||||
import {ServerConnectionFactory, setServerConnectionFactory} from "tc-shared/connection/ConnectionFactory";
|
import {ServerConnectionFactory, setServerConnectionFactory} from "tc-shared/connection/ConnectionFactory";
|
||||||
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
||||||
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
|
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
|
||||||
import {ServerConnection} from "tc-backend/web/connection/ServerConnection";
|
|
||||||
import * as loader from "tc-loader";
|
import * as loader from "tc-loader";
|
||||||
import {Stage} from "tc-loader";
|
import {Stage} from "tc-loader";
|
||||||
|
import {ServerConnection} from "../connection/ServerConnection";
|
||||||
|
|
||||||
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
|
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
priority: 50,
|
priority: 50,
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import * as loader from "tc-loader";
|
||||||
|
import {Stage} from "tc-loader";
|
||||||
|
import {setSoundBackend} from "tc-shared/audio/Sounds";
|
||||||
|
import {WebSoundBackend} from "../audio/Sounds";
|
||||||
|
|
||||||
|
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
|
name: "audio sound init",
|
||||||
|
function: async () => setSoundBackend(new WebSoundBackend()),
|
||||||
|
priority: 100
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
import "./AudioBackend";
|
||||||
|
import "./AudioRecorder";
|
||||||
|
import "./Backend";
|
||||||
|
import "./Dns";
|
||||||
|
import "./MenuBar";
|
||||||
|
import "./ServerConnection";
|
||||||
|
import "./Sounds";
|
||||||
|
import "./Video";
|
||||||
|
import "./KeyBoard";
|
|
@ -4,12 +4,7 @@ import "webcrypto-liner";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import "./FileTransfer";
|
import "./FileTransfer";
|
||||||
|
|
||||||
import "./hooks/ServerConnection";
|
import "./hooks"
|
||||||
import "./hooks/ExternalModal";
|
|
||||||
import "./hooks/AudioRecorder";
|
|
||||||
import "./hooks/MenuBar";
|
|
||||||
import "./hooks/Video";
|
|
||||||
import "./hooks/Dns";
|
|
||||||
|
|
||||||
import "./UnloadHandler";
|
import "./UnloadHandler";
|
||||||
|
|
||||||
|
|
151
web/app/ppt.ts
151
web/app/ppt.ts
|
@ -1,151 +0,0 @@
|
||||||
import {EventType, KeyEvent, KeyHook, SpecialKey} from "tc-shared/PPTListener";
|
|
||||||
import {LogCategory, logTrace} from "tc-shared/log";
|
|
||||||
import { tr } from "tc-shared/i18n/localize";
|
|
||||||
|
|
||||||
interface WebKeyEvent extends KeyEvent {
|
|
||||||
canceled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
let key_listener: ((_: KeyEvent) => any)[] = [];
|
|
||||||
|
|
||||||
function listener_key(type: EventType, event: KeyboardEvent) {
|
|
||||||
const key_event = {
|
|
||||||
type: type,
|
|
||||||
|
|
||||||
key: event.key,
|
|
||||||
key_code: event.code,
|
|
||||||
|
|
||||||
key_ctrl: event.ctrlKey,
|
|
||||||
key_shift: event.shiftKey,
|
|
||||||
key_alt: event.altKey,
|
|
||||||
key_windows: event.metaKey,
|
|
||||||
|
|
||||||
canceled: event.defaultPrevented
|
|
||||||
} as WebKeyEvent;
|
|
||||||
//console.debug("Trigger key event %o", key_event);
|
|
||||||
|
|
||||||
for(const listener of key_listener)
|
|
||||||
listener(key_event);
|
|
||||||
|
|
||||||
if(key_event.canceled)
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
const proxy_key_press = event => listener_key(EventType.KEY_PRESS, event);
|
|
||||||
const proxy_key_release = event => listener_key(EventType.KEY_RELEASE, event);
|
|
||||||
const proxy_key_typed = event => listener_key(EventType.KEY_TYPED, event);
|
|
||||||
|
|
||||||
export function initialize() : Promise<void> {
|
|
||||||
document.addEventListener('keypress', proxy_key_typed);
|
|
||||||
document.addEventListener('keydown', proxy_key_press);
|
|
||||||
document.addEventListener('keyup', proxy_key_release);
|
|
||||||
window.addEventListener('blur', listener_blur);
|
|
||||||
|
|
||||||
register_key_listener(listener_hook);
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function finalize() {
|
|
||||||
document.removeEventListener("keypress", proxy_key_typed);
|
|
||||||
document.removeEventListener("keydown", proxy_key_press);
|
|
||||||
document.removeEventListener("keyup", proxy_key_release);
|
|
||||||
window.removeEventListener('blur', listener_blur);
|
|
||||||
|
|
||||||
unregister_key_listener(listener_hook);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function register_key_listener(listener: (_: KeyEvent) => any) {
|
|
||||||
key_listener.push(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unregister_key_listener(listener: (_: KeyEvent) => any) {
|
|
||||||
key_listener.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let key_hooks: KeyHook[] = [];
|
|
||||||
|
|
||||||
interface CurrentState {
|
|
||||||
keys: {[code: string]:KeyEvent};
|
|
||||||
special: { [key:number]:boolean };
|
|
||||||
}
|
|
||||||
let current_state: CurrentState = {
|
|
||||||
special: []
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
let key_hooks_active: KeyHook[] = [];
|
|
||||||
|
|
||||||
function listener_blur() {
|
|
||||||
current_state.special[SpecialKey.ALT] = false;
|
|
||||||
current_state.special[SpecialKey.CTRL] = false;
|
|
||||||
current_state.special[SpecialKey.SHIFT] = false;
|
|
||||||
current_state.special[SpecialKey.WINDOWS] = false;
|
|
||||||
|
|
||||||
for(const code of Object.keys(current_state))
|
|
||||||
if(code !== "special")
|
|
||||||
delete current_state[code];
|
|
||||||
|
|
||||||
for(const hook of key_hooks_active)
|
|
||||||
hook.callback_release();
|
|
||||||
key_hooks_active = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function listener_hook(event: KeyEvent) {
|
|
||||||
if(event.type == EventType.KEY_TYPED)
|
|
||||||
return;
|
|
||||||
|
|
||||||
let old_hooks = [...key_hooks_active];
|
|
||||||
let new_hooks = [];
|
|
||||||
|
|
||||||
current_state.special[SpecialKey.ALT] = event.key_alt;
|
|
||||||
current_state.special[SpecialKey.CTRL] = event.key_ctrl;
|
|
||||||
current_state.special[SpecialKey.SHIFT] = event.key_shift;
|
|
||||||
current_state.special[SpecialKey.WINDOWS] = event.key_windows;
|
|
||||||
|
|
||||||
current_state[event.key_code] = undefined;
|
|
||||||
if(event.type == EventType.KEY_PRESS) {
|
|
||||||
current_state[event.key_code] = event;
|
|
||||||
|
|
||||||
for(const hook of key_hooks) {
|
|
||||||
if(hook.key_code !== event.key_code) continue;
|
|
||||||
if(hook.key_alt != event.key_alt) continue;
|
|
||||||
if(hook.key_ctrl != event.key_ctrl) continue;
|
|
||||||
if(hook.key_shift != event.key_shift) continue;
|
|
||||||
if(hook.key_windows != event.key_windows) continue;
|
|
||||||
|
|
||||||
new_hooks.push(hook);
|
|
||||||
if(!old_hooks.remove(hook) && hook.callback_press) {
|
|
||||||
hook.callback_press();
|
|
||||||
logTrace(LogCategory.GENERAL, tr("Trigger key press for %o!"), hook);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//We have a new situation
|
|
||||||
for(const hook of old_hooks) {
|
|
||||||
//Do not test for meta key states because they could differ in a key release event
|
|
||||||
if(hook.key_code === event.key_code) {
|
|
||||||
if(hook.callback_release) {
|
|
||||||
hook.callback_release();
|
|
||||||
logTrace(LogCategory.GENERAL, tr("Trigger key release for %o!"), hook);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
new_hooks.push(hook);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
key_hooks_active = new_hooks;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function register_key_hook(hook: KeyHook) {
|
|
||||||
key_hooks.push(hook);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unregister_key_hook(hook: KeyHook) {
|
|
||||||
key_hooks.remove(hook);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function key_pressed(code: string | SpecialKey) : boolean {
|
|
||||||
if(typeof(code) === 'string')
|
|
||||||
return typeof current_state[code] !== "undefined";
|
|
||||||
return current_state.special[code];
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {MenuBarDriver, MenuBarEntry} from "tc-shared/ui/frames/menu-bar";
|
import {MenuBarDriver, MenuBarEntry} from "tc-shared/ui/frames/menu-bar";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
import {MenuBarRenderer} from "tc-backend/web/ui/menu-bar/Renderer";
|
import {MenuBarRenderer} from "./Renderer";
|
||||||
|
|
||||||
const cssStyle = require("./Renderer.scss");
|
const cssStyle = require("./Renderer.scss");
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import * as aplayer from "../audio/player";
|
|
||||||
import {
|
import {
|
||||||
AbstractVoiceConnection,
|
AbstractVoiceConnection,
|
||||||
VoiceConnectionStatus,
|
VoiceConnectionStatus,
|
||||||
|
@ -17,9 +16,10 @@ import {AbstractServerConnection, ConnectionStatistics} from "tc-shared/connecti
|
||||||
import {VoicePlayerState} from "tc-shared/voice/VoicePlayer";
|
import {VoicePlayerState} from "tc-shared/voice/VoicePlayer";
|
||||||
import {LogCategory, logDebug, logError, logInfo, logTrace, logWarn} from "tc-shared/log";
|
import {LogCategory, logDebug, logError, logInfo, logTrace, logWarn} from "tc-shared/log";
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
import {RtpVoiceClient} from "tc-backend/web/voice/VoiceClient";
|
|
||||||
import {InputConsumerType} from "tc-shared/voice/RecorderBase";
|
import {InputConsumerType} from "tc-shared/voice/RecorderBase";
|
||||||
import {RtpWhisperSession} from "tc-backend/web/voice/WhisperClient";
|
import {getAudioBackend} from "tc-shared/audio/Player";
|
||||||
|
import {RtpVoiceClient} from "./VoiceClient";
|
||||||
|
import {RtpWhisperSession} from "./WhisperClient";
|
||||||
|
|
||||||
type CancelableWhisperTarget = WhisperTarget & { canceled: boolean };
|
type CancelableWhisperTarget = WhisperTarget & { canceled: boolean };
|
||||||
export class RtpVoiceConnection extends AbstractVoiceConnection {
|
export class RtpVoiceConnection extends AbstractVoiceConnection {
|
||||||
|
@ -86,8 +86,8 @@ export class RtpVoiceConnection extends AbstractVoiceConnection {
|
||||||
this.speakerMuted = connection.client.isSpeakerMuted() || connection.client.isSpeakerDisabled();
|
this.speakerMuted = connection.client.isSpeakerMuted() || connection.client.isSpeakerDisabled();
|
||||||
|
|
||||||
this.setConnectionState(VoiceConnectionStatus.Disconnected);
|
this.setConnectionState(VoiceConnectionStatus.Disconnected);
|
||||||
aplayer.on_ready(() => {
|
getAudioBackend().executeWhenInitialized(() => {
|
||||||
this.localAudioDestination = aplayer.context().createMediaStreamDestination();
|
this.localAudioDestination = getAudioBackend().getAudioContext().createMediaStreamDestination();
|
||||||
if(this.currentAudioSourceNode) {
|
if(this.currentAudioSourceNode) {
|
||||||
this.currentAudioSourceNode.connect(this.localAudioDestination);
|
this.currentAudioSourceNode.connect(this.localAudioDestination);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import {VoiceClient} from "tc-shared/voice/VoiceClient";
|
import {VoiceClient} from "tc-shared/voice/VoiceClient";
|
||||||
import {VoicePlayer} from "./VoicePlayer";
|
import {VoicePlayer} from "./VoicePlayer";
|
||||||
import {LogCategory, logTrace} from "tc-shared/log";
|
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
|
||||||
import {RemoteRTPAudioTrack} from "tc-shared/connection/rtc/RemoteTrack";
|
|
||||||
|
|
||||||
export class RtpVoiceClient extends VoicePlayer implements VoiceClient {
|
export class RtpVoiceClient extends VoicePlayer implements VoiceClient {
|
||||||
private readonly clientId: number;
|
private readonly clientId: number;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {VoicePlayerEvents, VoicePlayerLatencySettings, VoicePlayerState} from "tc-shared/voice/VoicePlayer";
|
import {VoicePlayerEvents, VoicePlayerLatencySettings, VoicePlayerState} from "tc-shared/voice/VoicePlayer";
|
||||||
import {Registry} from "tc-shared/events";
|
import {Registry} from "tc-shared/events";
|
||||||
import {LogCategory, logTrace, logWarn} from "tc-shared/log";
|
import {LogCategory, logWarn} from "tc-shared/log";
|
||||||
import {RemoteRTPAudioTrack, RemoteRTPTrackState} from "tc-shared/connection/rtc/RemoteTrack";
|
import {RemoteRTPAudioTrack, RemoteRTPTrackState} from "tc-shared/connection/rtc/RemoteTrack";
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
import {tr} from "tc-shared/i18n/localize";
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,6 @@ export = env => config_base.config(env, "client").then(config => {
|
||||||
|
|
||||||
Object.assign(config.resolve.alias, {
|
Object.assign(config.resolve.alias, {
|
||||||
"tc-shared": path.resolve(__dirname, "shared/js"),
|
"tc-shared": path.resolve(__dirname, "shared/js"),
|
||||||
/* backend hasn't declared but its available via "require()" */
|
|
||||||
"tc-backend": path.resolve(__dirname, "shared/backend.d"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if(!Array.isArray(config.externals))
|
if(!Array.isArray(config.externals))
|
||||||
|
|
|
@ -9,8 +9,6 @@ export = env => config_base.config(env, "web").then(config => {
|
||||||
|
|
||||||
Object.assign(config.resolve.alias, {
|
Object.assign(config.resolve.alias, {
|
||||||
"tc-shared": path.resolve(__dirname, "shared/js"),
|
"tc-shared": path.resolve(__dirname, "shared/js"),
|
||||||
"tc-backend/web": path.resolve(__dirname, "web/app"),
|
|
||||||
"tc-backend": path.resolve(__dirname, "web/app"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.resolve(config);
|
return Promise.resolve(config);
|
||||||
|
|
Loading…
Reference in New Issue