Finally got fully rid of the initial backend glue and changes all systems to provider

master
WolverinDEV 2021-03-18 18:25:20 +01:00
parent 38ea11725f
commit e2be859266
61 changed files with 620 additions and 579 deletions

View File

@ -1,4 +1,7 @@
# Changelog:
* **18.03.21**
- Finally got fully rid of the initial backend glue and changes all systems to provider
* **17.03.21**
- Updated from webpack 4 to webpack 5
- Reworked the client build process

View File

@ -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)).

View File

@ -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;

View File

@ -1,3 +0,0 @@
import {SoundFile} from "tc-shared/sound/Sounds";
export function play_sound(file: SoundFile) : Promise<void>;

View File

@ -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;

View File

@ -1,2 +0,0 @@
This folder contains declarations files which are required to be implemented
Else the UI shared pack wound work

View File

@ -2,7 +2,7 @@ import {AbstractServerConnection} from "./connection/ConnectionBase";
import {PermissionManager} from "./permission/PermissionManager";
import {GroupManager} from "./permission/GroupManager";
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 {LogCategory, logError, logInfo, logTrace, logWarn} from "./log";
import {createErrorModal, createInfoModal, createInputModal, Modal} from "./ui/elements/Modal";

View File

@ -1,7 +1,5 @@
import * as ppt from "tc-backend/ppt";
import * as log 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 {server_connections} from "tc-shared/ConnectionManager";
import {tr} from "./i18n/localize";
@ -181,18 +179,18 @@ function bindKey(action: string, key: KeyDescriptor) {
keyBindings[action] = {
hook: Object.assign({
callback_press: () => control.handler(),
callback_release: () => {},
callbackPress: () => control.handler(),
callbackRelease: () => {},
cancel: false
}, key),
binding: key
};
ppt.register_key_hook(keyBindings[action].hook);
getKeyBoard().registerHook(keyBindings[action].hook);
}
export function setKey(action: string, key?: KeyDescriptor) {
if(typeof keyBindings[action] !== "undefined") {
ppt.unregister_key_hook(keyBindings[action].hook);
getKeyBoard().unregisterHook(keyBindings[action].hook);
delete keyBindings[action];
}

View File

@ -1,4 +1,5 @@
import { tr } from "./i18n/localize";
import {LogCategory, logTrace} from "tc-shared/log";
export enum KeyCode {
KEY_CANCEL = 3,
@ -134,57 +135,199 @@ export enum SpecialKey {
}
export interface KeyDescriptor {
key_code: string;
keyCode: string;
key_ctrl: boolean;
key_windows: boolean;
key_shift: boolean;
key_alt: boolean;
keyCtrl: boolean;
keyWindows: boolean;
keyShift: boolean;
keyAlt: boolean;
}
export interface KeyEvent extends KeyDescriptor {
readonly type: EventType;
readonly key: string;
}
export interface KeyHook extends KeyDescriptor {
cancel: boolean;
callback_press: () => any;
callback_release: () => any;
callbackPress: () => any;
callbackRelease: () => 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 = "";
if(key.key_shift) {
if(key.keyShift) {
result += " + " + tr("Shift");
}
if(key.key_alt) {
if(key.keyAlt) {
result += " + " + tr("Alt");
}
if(key.key_ctrl) {
if(key.keyCtrl) {
result += " + " + tr("CTRL");
}
if(key.key_windows) {
if(key.keyWindows) {
result += " + " + tr("Win");
}
if(key.key_code) {
let key_name;
if(key.key_code.startsWith("Key")) {
key_name = key.key_code.substr(3);
} else if(key.key_code.startsWith("Digit")) {
key_name = key.key_code.substr(5);
} else if(key.key_code.startsWith("Numpad")) {
key_name = "Numpad " + key.key_code.substr(6);
if(key.keyCode) {
let keyName;
if(key.keyCode.startsWith("Key")) {
keyName = key.keyCode.substr(3);
} else if(key.keyCode.startsWith("Digit")) {
keyName = key.keyCode.substr(5);
} else if(key.keyCode.startsWith("Numpad")) {
keyName = "Numpad " + key.keyCode.substr(6);
} else {
key_name = key.key_code;
keyName = key.keyCode;
}
result += " + " + key_name;
result += " + " + keyName;
}
return result ? result.substr(3) : tr("unset");
}

39
shared/js/audio/Player.ts Normal file
View File

@ -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;
}

View File

@ -4,11 +4,9 @@ import {AbstractInput, LevelMeter} from "../voice/RecorderBase";
import {Registry} from "../events";
import {Settings, settings} from "tc-shared/settings";
export type DeviceQueryResult = {}
export interface AudioRecorderBacked {
createInput() : AbstractInput;
createLevelMeter(device: IDevice) : Promise<LevelMeter>;
createLevelMeter(device: InputDevice) : Promise<LevelMeter>;
getDeviceList() : DeviceList;
@ -35,14 +33,14 @@ export interface DeviceListEvents {
export type DeviceListState = "healthy" | "uninitialized" | "no-permissions" | "error";
export interface IDevice {
export interface InputDevice {
deviceId: string;
driver: string;
name: string;
}
export namespace IDevice {
export namespace InputDevice {
export const NoDeviceId = "none";
export const DefaultDeviceId = "default";
}
@ -60,7 +58,7 @@ export interface DeviceList {
getPermissionState() : PermissionState;
getStatus() : DeviceListState;
getDevices() : IDevice[];
getDevices() : InputDevice[];
getDefaultDeviceId() : string;
@ -144,7 +142,7 @@ export abstract class AbstractDeviceList implements DeviceList {
}
abstract getDefaultDeviceId(): string;
abstract getDevices(): IDevice[];
abstract getDevices(): InputDevice[];
abstract getEvents(): Registry<DeviceListEvents>;
abstract isRefreshAvailable(): boolean;
abstract refresh(): Promise<void>;

View File

@ -1,8 +1,6 @@
import * as log from "../log";
import {LogCategory, logError, logInfo, logWarn} from "../log";
import {Settings, settings} from "../settings";
import {ConnectionHandler} from "../ConnectionHandler";
import * as sbackend from "tc-backend/audio/sounds";
import { tr } from "tc-shared/i18n/localize";
export enum Sound {
@ -226,7 +224,6 @@ export async function resolve_sound(sound: Sound) : Promise<SoundHandle> {
}
export let manager: SoundManager;
export class SoundManager {
private readonly _handle: ConnectionHandler;
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;
sbackend.play_sound({
getSoundBackend().playSound({
path: "audio/" + handle.filename,
volume: volume * master_volume
}).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;
}

View File

@ -1,6 +0,0 @@
export interface Device {
device_id: string;
driver: string;
name: string;
}

View File

@ -1,6 +1,6 @@
import {LogCategory, logError, logInfo, logWarn} from "../log";
import {AbstractServerConnection, CommandOptions, ServerCommand} from "../connection/ConnectionBase";
import {Sound} from "../sound/Sounds";
import {Sound} from "../audio/Sounds";
import {CommandResult} from "../connection/ServerConnectionDeclaration";
import {createErrorModal, createInfoModal, createInputModal, createModal} from "../ui/elements/Modal";
import {

View File

@ -9,9 +9,9 @@ import {RemoteRTPAudioTrack, RemoteRTPTrackState, RemoteRTPVideoTrack, TrackClie
import {SdpCompressor, SdpProcessor} from "./SdpUtils";
import {ErrorCode} from "tc-shared/connection/ErrorCode";
import {WhisperTarget} from "tc-shared/voice/VoiceWhisper";
import {globalAudioContext} from "tc-backend/audio/player";
import {VideoBroadcastConfig, VideoBroadcastType} from "tc-shared/connection/VideoConnection";
import {Settings, settings} from "tc-shared/settings";
import {getAudioBackend} from "tc-shared/audio/Player";
const kSdpCompressionMode = 1;
@ -144,7 +144,7 @@ function getIdleTrack(kind: "video" | "audio") : MediaStreamTrack | null {
return dummyVideoTrack;
} else if(kind === "audio") {
if(!dummyAudioTrack) {
const dest = globalAudioContext().createMediaStreamDestination();
const dest = getAudioBackend().getAudioContext().createMediaStreamDestination();
dummyAudioTrack = dest.stream.getAudioTracks()[0];
}

View File

@ -1,7 +1,7 @@
import {Registry} from "tc-shared/events";
import {LogCategory, logTrace, logWarn} from "tc-shared/log";
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 {
media?: number,
@ -170,13 +170,13 @@ export class RemoteRTPAudioTrack extends RemoteRTPTrack {
}
*/
on_ready(() => {
getAudioBackend().executeWhenInitialized(() => {
if(!this.mediaStream) {
/* we've already been destroyed */
return;
}
const audioContext = globalAudioContext();
const audioContext = getAudioBackend().getAudioContext();
this.audioNode = audioContext.createMediaStreamSource(this.mediaStream);
this.gainNode = audioContext.createGain();
this.updateGainNode();

View File

@ -1,6 +1,5 @@
import {Registry} from "../events";
import {ClientGlobalControlEvents} from "../events/GlobalEvents";
import {Sound} from "../sound/Sounds";
import {ConnectionHandler} from "../ConnectionHandler";
import {createErrorModal, createInfoModal, createInputModal} from "../ui/elements/Modal";
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 {spawnConnectModalNew} from "tc-shared/ui/modal/connect/Controller";
import {spawnBookmarkModal} from "tc-shared/ui/modal/bookmarks/Controller";
import {Sound} from "tc-shared/audio/Sounds";
/*
function initialize_sounds(event_registry: Registry<ClientGlobalControlEvents>) {

View File

@ -1,12 +1,9 @@
import * as loader from "tc-loader";
import {Stage} from "tc-loader";
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 {tra} from "./i18n/localize";
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 {AppParameters, settings, Settings, UrlParameterBuilder, UrlParameterParser} from "tc-shared/settings";
import {LogCategory, logDebug, logError, logInfo, logWarn} from "tc-shared/log";
@ -55,6 +52,7 @@ import "./ui/elements/Tab";
import "./clientservice";
import "./text/bbcode/InviteController";
import "./text/bbcode/YoutubeController";
import {getAudioBackend} from "tc-shared/audio/Player";
assertMainApplication();
@ -73,12 +71,7 @@ async function initialize() {
async function initializeApp() {
global_ev_handler.initialize(global_client_actions);
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));
getAudioBackend().setMasterVolume(settings.getValue(Settings.KEY_SOUND_MASTER) / 100);
const recorder = new RecorderProfile("default");
try {
@ -93,14 +86,6 @@ async function initializeApp() {
logInfo(LogCategory.AUDIO, tr("Sounds initialized"));
});
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. */
@ -200,7 +185,7 @@ async function doHandleConnectRequest(serverAddress: string, serverUniqueId: str
return { status: "profile-invalid" };
}
if(!aplayer.initialized()) {
if(!getAudioBackend().isInitialized()) {
/* Trick the client into clicking somewhere on the site to initialize audio */
const resultPromise = new Promise<boolean>(resolve => {
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" };
}
await new Promise(resolve => aplayer.on_ready(resolve));
await new Promise(resolve => getAudioBackend().executeWhenInitialized(resolve));
}
const clientNickname = parameters.getValue(AppParameters.KEY_CONNECT_NICKNAME, undefined);
@ -403,15 +388,6 @@ const task_teaweb_starter: loader.Task = {
try {
await initializeApp();
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);
} catch (ex) {
console.error(ex.stack);

View File

@ -6,10 +6,9 @@ import {PermissionType} from "../permission/PermissionType";
import {settings, Settings} from "../settings";
import * as contextmenu 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 {CommandResult} from "../connection/ServerConnectionDeclaration";
import * as htmltags from "../ui/htmltags";
import {hashPassword} from "../utils/helpers";
import {openChannelInfo} from "../ui/modal/ModalChannelInfo";
import {formatMessage} from "../ui/frames/chat";

View File

@ -2,7 +2,7 @@ import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
import {MenuEntryType} from "tc-shared/ui/elements/ContextMenu";
import {LogCategory, logDebug, logError, logWarn} from "tc-shared/log";
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 {ServerAddress, ServerEntry} from "./Server";
import {ChannelEntry, ChannelProperties, ChannelSubscribeMode} from "./Channel";

View File

@ -4,7 +4,7 @@ import {ChannelTree} from "./ChannelTree";
import * as log from "../log";
import {LogCategory, logDebug, logError, logInfo, LogType} from "../log";
import {Settings, settings} from "../settings";
import {Sound} from "../sound/Sounds";
import {Sound} from "../audio/Sounds";
import {Group, GroupManager, GroupTarget, GroupType} from "../permission/GroupManager";
import PermissionType from "../permission/PermissionType";
import {createErrorModal, createInputModal} from "../ui/elements/Modal";

View File

@ -3,7 +3,7 @@ import {Settings, settings} from "../settings";
import * as contextmenu from "../ui/elements/ContextMenu";
import * as log 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 {createServerModal} from "../ui/modal/ModalServerEdit";
import {spawnIconSelect} from "../ui/modal/ModalIconSelect";

View File

@ -16,7 +16,7 @@ import {VideoBroadcastType, VideoConnectionStatus} from "tc-shared/connection/Vi
import {tr} from "tc-shared/i18n/localize";
import {getVideoDriver} from "tc-shared/video/VideoSource";
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 {bookmarks} from "tc-shared/Bookmarks";
import {connectionHistory} from "tc-shared/connectionlog/History";
@ -276,7 +276,7 @@ class InfoController {
this.events.fire_react("notify_microphone_list", {
devices: devices.map(device => {
let selected = false;
if(selectedDevice === IDevice.DefaultDeviceId && device.deviceId === defaultDevice) {
if(selectedDevice === InputDevice.DefaultDeviceId && device.deviceId === defaultDevice) {
selected = true;
} else if(selectedDevice === device.deviceId) {
selected = true;

View File

@ -1,6 +1,5 @@
import {createModal} from "../../ui/elements/Modal";
import {EventType, key_description, KeyEvent} from "../../PPTListener";
import * as ppt from "tc-backend/ppt";
import {EventType, getKeyBoard, getKeyDescription, KeyEvent} from "../../PPTListener";
import {tr} from "tc-shared/i18n/localize";
export function spawnKeySelect(callback: (key?: KeyEvent) => void) {
@ -27,7 +26,7 @@ export function spawnKeySelect(callback: (key?: KeyEvent) => void) {
current_key = event;
current_key_age = Date.now();
container_key.text(key_description(event));
container_key.text(getKeyDescription(event));
button_save.prop("disabled", false);
}
};
@ -36,7 +35,7 @@ export function spawnKeySelect(callback: (key?: KeyEvent) => void) {
button_save.on('click', () => {
if (__build.version !== "web") {
/* 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;
}
@ -45,8 +44,9 @@ export function spawnKeySelect(callback: (key?: KeyEvent) => void) {
}).prop("disabled", true);
button_cancel.on('click', () => modal.close());
ppt.register_key_listener(listener);
modal.close_listener.push(() => ppt.unregister_key_listener(listener));
const keyboard = getKeyBoard();
keyboard.registerListener(listener);
modal.close_listener.push(() => keyboard.unregisterListener(listener));
modal.htmlTag.find(".modal-body").addClass("modal-keyselect modal-green");
modal.open();

View File

@ -1,8 +1,8 @@
import {createErrorModal, createInfoModal, createInputModal, createModal, Modal} from "tc-shared/ui/elements/Modal";
import {sliderfy} from "tc-shared/ui/elements/Slider";
import {settings, Settings} from "tc-shared/settings";
import * as sound from "tc-shared/sound/Sounds";
import {manager, set_master_volume, Sound} from "tc-shared/sound/Sounds";
import * as sound from "tc-shared/audio/Sounds";
import {manager, set_master_volume, Sound} from "tc-shared/audio/Sounds";
import * as profiles from "tc-shared/profiles/ConnectionProfile";
import {ConnectionProfile} from "tc-shared/profiles/ConnectionProfile";
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 {formatMessage, set_icon_size} from "tc-shared/ui/frames/chat";
import {spawnTeamSpeakIdentityImport, spawnTeamSpeakIdentityImprove} from "tc-shared/ui/modal/ModalIdentity";
import {Device} from "tc-shared/audio/player";
import * as aplayer from "tc-backend/audio/player";
import {getAudioBackend, OutputDevice} from "tc-shared/audio/Player";
import {KeyMapSettings} from "tc-shared/ui/modal/settings/Keymap";
import * as React from "react";
import * as ReactDOM from "react-dom";
@ -628,8 +627,8 @@ function settings_audio_speaker(container: JQuery, modal: Modal) {
const update_devices = () => {
container_devices.children().remove();
const current_selected = aplayer.current_device();
const generate_device = (device: Device | undefined) => {
const current_selected = getAudioBackend().getCurrentDevice();
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 tag = $.spawn("div").addClass("device").toggleClass("selected", selected).append(
@ -654,7 +653,7 @@ function settings_audio_speaker(container: JQuery, modal: Modal) {
_old.removeClass("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"));
}).catch((error) => {
_old.addClass("selected");
@ -669,7 +668,7 @@ function settings_audio_speaker(container: JQuery, modal: Modal) {
};
generate_device(undefined).appendTo(container_devices);
aplayer.available_devices().then(result => {
getAudioBackend().getAvailableDevices().then(result => {
contianer_error.text("").hide();
result.forEach(e => generate_device(e).appendTo(container_devices));
}).catch(error => {
@ -710,8 +709,7 @@ function settings_audio_speaker(container: JQuery, modal: Modal) {
slider.on('change', event => {
const volume = parseInt(slider.attr('value'));
if (aplayer.set_master_volume)
aplayer.set_master_volume(volume / 100);
getAudioBackend().setMasterVolume(volume / 100);
settings.setValue(Settings.KEY_SOUND_MASTER, volume);
});
}

View File

@ -86,7 +86,7 @@ class KeyActionEntry extends ReactComponentBase<KeyActionEntryProperties, KeyAct
} else if (this.state.state === "loaded") {
rightItem = null;
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 {
rightItem =
<div key={"status-error"} className={this.classList(cssStyle.status, cssStyle.error)}><Translatable

View File

@ -1,13 +1,13 @@
import * as aplayer from "tc-backend/audio/player";
import * as React from "react";
import {Registry} from "tc-shared/events";
import {LevelMeter} from "tc-shared/voice/RecorderBase";
import {LogCategory, logTrace, logWarn} from "tc-shared/log";
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 {getBackend} from "tc-shared/backend";
import * as _ from "lodash";
import {getAudioBackend} from "tc-shared/audio/Player";
export type MicrophoneSetting =
"volume"
@ -223,9 +223,9 @@ export function initialize_audio_microphone_controller(events: Registry<Micropho
{
const currentSelectedDevice = (): SelectedMicrophone => {
let deviceId = defaultRecorder.getDeviceId();
if(deviceId === IDevice.DefaultDeviceId) {
if(deviceId === InputDevice.DefaultDeviceId) {
return { type: "default" };
} else if(deviceId === IDevice.NoDeviceId) {
} else if(deviceId === InputDevice.NoDeviceId) {
return { type: "none" };
} else {
return { type: "device", deviceId: deviceId };
@ -233,7 +233,7 @@ export function initialize_audio_microphone_controller(events: Registry<Micropho
};
events.on("query_devices", event => {
if (!aplayer.initialized()) {
if (!getAudioBackend().isInitialized()) {
events.fire_react("notify_devices", {
status: "audio-not-initialized"
});
@ -452,10 +452,8 @@ export function initialize_audio_microphone_controller(events: Registry<Micropho
events.fire("query_devices");
}));
if (!aplayer.initialized()) {
aplayer.on_ready(() => {
events.fire_react("query_devices");
});
if(!getAudioBackend().isInitialized()) {
getAudioBackend().executeWhenInitialized(() => events.fire_react("query_devices"));
}
}

View File

@ -11,12 +11,12 @@ import {createErrorModal} from "tc-shared/ui/elements/Modal";
import {Slider} from "tc-shared/ui/react-elements/Slider";
import {RadioButton} from "tc-shared/ui/react-elements/RadioButton";
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 {Checkbox} from "tc-shared/ui/react-elements/Checkbox";
import {BoxedInputField} from "tc-shared/ui/react-elements/InputField";
import {IDevice} from "tc-shared/audio/recorder";
import {HighlightContainer, HighlightRegion, HighlightText} from "./Heighlight";
import {InputDevice} from "tc-shared/audio/Recorder";
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>
<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}/>
}
</div>
@ -401,7 +401,7 @@ const PPTKeyButton = React.memo((props: { events: Registry<MicrophoneSettingsEve
props.events.fire("action_set_setting", {setting: "ppt-key", value: key});
});
}}
>{key_description(key)}</Button>;
>{getKeyDescription(key)}</Button>;
}
});

View File

@ -5,8 +5,7 @@ import {CommandResult} from "../../../connection/ServerConnectionDeclaration";
import PermissionType from "../../../permission/PermissionType";
import {LogCategory, logError, logTrace} from "../../../log";
import {Entry, MenuEntry, MenuEntryType, spawn_context_menu} from "../../../ui/elements/ContextMenu";
import * as ppt from "tc-backend/ppt";
import {SpecialKey} from "../../../PPTListener";
import {getKeyBoard, SpecialKey} from "../../../PPTListener";
import {spawnYesNo} from "../../../ui/modal/ModalYesNo";
import {tr, tra, traj} from "../../../i18n/localize";
import {
@ -427,7 +426,7 @@ export function initializeRemoteFileBrowserController(connection: ConnectionHand
icon_class: "client-file_refresh"
});
} else {
const forceDelete = ppt.key_pressed(SpecialKey.SHIFT);
const forceDelete = getKeyBoard().isKeyPressed(SpecialKey.SHIFT);
if (selection.length === 0) {
entries.push({
type: MenuEntryType.ENTRY,

View File

@ -1,8 +1,7 @@
import {EventHandler, ReactEventHandler, Registry} from "tc-shared/events";
import {useContext, useEffect, useRef, useState} from "react";
import {FileType} from "tc-shared/file/FileManager";
import * as ppt from "tc-backend/ppt";
import {SpecialKey} from "tc-shared/PPTListener";
import {getKeyBoard, SpecialKey} from "tc-shared/PPTListener";
import {createErrorModal} from "tc-shared/ui/elements/Modal";
import {tra} from "tc-shared/i18n/localize";
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")
return;
if (!ppt.key_pressed(SpecialKey.SHIFT))
if (!getKeyBoard().isKeyPressed(SpecialKey.SHIFT))
return;
event.stopPropagation();
@ -656,7 +655,7 @@ const FileListEntry = (props: { row: TableRow<ListedFileInfo>, columns: TableCol
onClick={() => props.events.fire("action_select_files", {
files: [{name: file.name, type: file.type}],
mode: ppt.key_pressed(SpecialKey.SHIFT) ? "toggle" : "exclusive"
mode: getKeyBoard().isKeyPressed(SpecialKey.SHIFT) ? "toggle" : "exclusive"
})}
onContextMenu={e => {
if (!selected) {
@ -664,7 +663,7 @@ const FileListEntry = (props: { row: TableRow<ListedFileInfo>, columns: TableCol
/* explicitly clicked on one file */
props.events.fire("action_select_files", {
files: [{name: file.name, type: file.type}],
mode: ppt.key_pressed(SpecialKey.SHIFT) ? "toggle" : "exclusive"
mode: getKeyBoard().isKeyPressed(SpecialKey.SHIFT) ? "toggle" : "exclusive"
});
} else {
props.events.fire("action_select_files", {files: [], mode: "exclusive"});

View File

@ -1,4 +1,4 @@
import {IDevice} from "../audio/recorder";
import {InputDevice} from "../audio/Recorder";
import {Registry} from "../events";
import {Filter, FilterType, FilterTypeClass} from "../voice/Filter";
@ -119,7 +119,7 @@ export interface AbstractInput {
}
export interface LevelMeter {
getDevice() : IDevice;
getDevice() : InputDevice;
setObserver(callback: (value: number) => any);

View File

@ -1,15 +1,13 @@
import * as log from "../log";
import {LogCategory, logDebug, logError, logWarn} from "../log";
import {AbstractInput, FilterMode} from "../voice/RecorderBase";
import {KeyDescriptor, KeyHook} from "../PPTListener";
import {getKeyBoard, KeyDescriptor, KeyHook} from "../PPTListener";
import {Settings, settings} from "../settings";
import {ConnectionHandler} from "../ConnectionHandler";
import * as aplayer from "tc-backend/audio/player";
import * as ppt from "tc-backend/ppt";
import {getRecorderBackend, IDevice} from "../audio/recorder";
import {getRecorderBackend, InputDevice} from "../audio/Recorder";
import {FilterType, StateFilter, ThresholdFilter} from "../voice/Filter";
import { tr } from "tc-shared/i18n/localize";
import {Registry} from "tc-shared/events";
import {getAudioBackend} from "tc-shared/audio/Player";
export type VadType = "threshold" | "push_to_talk" | "active";
export interface RecorderProfileConfig {
@ -85,7 +83,7 @@ export class RecorderProfile {
this.volatile = typeof(volatile) === "boolean" ? volatile : false;
this.pptHook = {
callback_release: () => {
callbackRelease: () => {
if(this.pptTimeout)
clearTimeout(this.pptTimeout);
@ -94,14 +92,12 @@ export class RecorderProfile {
}, Math.max(this.config.vad_push_to_talk.delay, 0));
},
callback_press: () => {
callbackPress: () => {
if(this.pptTimeout)
clearTimeout(this.pptTimeout);
this.registeredFilter["ppt-gate"]?.setState(false);
},
cancel: false
} as KeyHook;
this.pptHookRegistered = false;
}
@ -125,7 +121,7 @@ export class RecorderProfile {
/* default values */
this.config = {
version: 1,
device_id: IDevice.DefaultDeviceId,
device_id: InputDevice.DefaultDeviceId,
volume: 100,
vad_threshold: {
@ -145,7 +141,7 @@ export class RecorderProfile {
Object.assign(this.config, config || {});
}
aplayer.on_ready(async () => {
getAudioBackend().executeWhenInitialized(async () => {
await getRecorderBackend().getDeviceList().awaitInitialized();
await this.initializeInput();
@ -185,7 +181,7 @@ export class RecorderProfile {
if(this.config.device_id) {
await this.input.setDeviceId(this.config.device_id);
} else {
await this.input.setDeviceId(IDevice.DefaultDeviceId);
await this.input.setDeviceId(InputDevice.DefaultDeviceId);
}
}
@ -201,15 +197,12 @@ export class RecorderProfile {
}
if(this.pptHookRegistered) {
ppt.unregister_key_hook(this.pptHook);
getKeyBoard().unregisterHook(this.pptHook);
this.pptHookRegistered = false;
}
for(const key of ["key_alt", "key_ctrl", "key_shift", "key_windows", "key_code"]) {
this.pptHook[key] = this.config.vad_push_to_talk[key];
}
ppt.register_key_hook(this.pptHook);
Object.assign(this.pptHook, this.getPushToTalkKey());
getKeyBoard().registerHook(this.pptHook);
this.pptHookRegistered = true;
this.registeredFilter["ppt-gate"]?.setState(true);
@ -227,7 +220,7 @@ export class RecorderProfile {
this.registeredFilter["ppt-gate"].setEnabled(false);
if(this.pptHookRegistered) {
ppt.unregister_key_hook(this.pptHook);
getKeyBoard().unregisterHook(this.pptHook);
this.pptHookRegistered = false;
}
@ -251,10 +244,8 @@ export class RecorderProfile {
filter.setEnabled(true);
filter.setState(true); /* by default set filtered */
for(const key of ["key_alt", "key_ctrl", "key_shift", "key_windows", "key_code"])
this.pptHook[key] = this.config.vad_push_to_talk[key];
ppt.register_key_hook(this.pptHook);
Object.assign(this.pptHook, this.getPushToTalkKey());
getKeyBoard().registerHook(this.pptHook);
this.pptHookRegistered = true;
} else if(this.config.vad_type === "active") {
/* we don't have to initialize any filters */
@ -311,10 +302,27 @@ export class RecorderProfile {
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) {
for(const _key of ["key_alt", "key_ctrl", "key_shift", "key_windows", "key_code"])
this.config.vad_push_to_talk[_key] = key[_key];
this.config.vad_push_to_talk = {
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.save();
@ -329,8 +337,8 @@ export class RecorderProfile {
this.save();
}
getDeviceId() : string | typeof IDevice.DefaultDeviceId | typeof IDevice.NoDeviceId { return this.config.device_id; }
setDevice(device: IDevice | typeof IDevice.DefaultDeviceId | typeof IDevice.NoDeviceId) : Promise<void> {
getDeviceId() : string | typeof InputDevice.DefaultDeviceId | typeof InputDevice.NoDeviceId { return this.config.device_id; }
setDevice(device: InputDevice | typeof InputDevice.DefaultDeviceId | typeof InputDevice.NoDeviceId) : Promise<void> {
let deviceId;
if(typeof device === "object") {
deviceId = device.deviceId;

View File

@ -11,7 +11,6 @@
"baseUrl": "../../",
"paths": {
"tc-shared/*": ["shared/js/*"],
"tc-backend/*": ["shared/backend.d/*"],
"tc-loader": ["loader/exports/loader.d.ts"],
"svg-sprites/*": ["shared/svg-sprites/*"],
"vendor/xbbcode/*": ["vendor/xbbcode/src/*"],

View File

@ -12,9 +12,6 @@
"baseUrl": ".",
"paths": {
"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-events": ["vendor/TeaEventBus/src/index.ts"],
"tc-services": ["vendor/TeaClientServices/src/index.ts"],

View File

@ -12,7 +12,6 @@ import {
TransferSourceType,
TransferTargetType
} from "tc-shared/file/Transfer";
import * as log from "tc-shared/log";
import {LogCategory, logError} from "tc-shared/log";
import { tr } from "tc-shared/i18n/localize";

49
web/app/KeyBoard.ts Normal file
View File

@ -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();
}
}

148
web/app/audio/Player.ts Normal file
View File

@ -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;
}
}

View File

@ -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 {
AbstractInput,
@ -12,12 +12,12 @@ import {
NodeInputConsumer
} from "tc-shared/voice/RecorderBase";
import {LogCategory, logDebug, logWarn} from "tc-shared/log";
import * as aplayer from "./player";
import {JAbstractFilter, JStateFilter, JThresholdFilter} from "./RecorderFilter";
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 {tr} from "tc-shared/i18n/localize";
import {getAudioBackend} from "tc-shared/audio/Player";
declare global {
interface MediaStream {
@ -25,7 +25,7 @@ declare global {
}
}
export interface WebIDevice extends IDevice {
export interface WebIDevice extends InputDevice {
groupId: string;
}
@ -34,7 +34,7 @@ export class WebAudioRecorder implements AudioRecorderBacked {
return new JavascriptInput();
}
async createLevelMeter(device: IDevice): Promise<LevelMeter> {
async createLevelMeter(device: InputDevice): Promise<LevelMeter> {
const meter = new JavascriptLevelMeter(device as any);
await meter.initialize();
return meter;
@ -81,14 +81,14 @@ class JavascriptInput implements AbstractInput {
constructor() {
this.events = new Registry<InputEvents>();
aplayer.on_ready(() => this.handleAudioInitialized());
getAudioBackend().executeWhenInitialized(() => this.handleAudioInitialized());
this.audioScriptProcessorCallback = this.handleAudio.bind(this);
}
destroy() { }
private handleAudioInitialized() {
this.audioContext = aplayer.context();
this.audioContext = getAudioBackend().getAudioContext();
this.audioNodeMute = this.audioContext.createGain();
this.audioNodeMute.gain.value = 0;
this.audioNodeMute.connect(this.audioContext.destination);
@ -179,7 +179,7 @@ class JavascriptInput implements AbstractInput {
private async doStart() : Promise<InputStartError | true> {
try {
if(!aplayer.initialized() || !this.audioContext) {
if(!getAudioBackend().isInitialized() || !this.audioContext) {
return InputStartError.ESYSTEMUNINITIALIZED;
}
@ -189,9 +189,9 @@ class JavascriptInput implements AbstractInput {
this.setState(InputState.INITIALIZING);
let deviceId;
if(this.deviceId === IDevice.NoDeviceId) {
if(this.deviceId === InputDevice.NoDeviceId) {
throw tr("no device selected");
} else if(this.deviceId === IDevice.DefaultDeviceId) {
} else if(this.deviceId === InputDevice.DefaultDeviceId) {
deviceId = undefined;
} else {
deviceId = this.deviceId;
@ -511,7 +511,7 @@ class JavascriptLevelMeter implements LevelMeter {
try {
await new Promise((resolve, reject) => {
const timeout = setTimeout(reject, 5000);
aplayer.on_ready(() => {
getAudioBackend().executeWhenInitialized(() => {
clearTimeout(timeout);
resolve();
});
@ -519,7 +519,7 @@ class JavascriptLevelMeter implements LevelMeter {
} catch(error) {
throw tr("audio context timeout");
}
this._context = aplayer.context();
this._context = getAudioBackend().getAudioContext();
if(!this._context) throw tr("invalid context");
this._gain_node = this._context.createGain();
@ -591,7 +591,7 @@ class JavascriptLevelMeter implements LevelMeter {
}
}
getDevice(): IDevice {
getDevice(): InputDevice {
return this._device;
}

View File

@ -2,13 +2,12 @@ import {
AbstractDeviceList,
DeviceListEvents,
DeviceListState,
IDevice,
InputDevice,
PermissionState
} from "tc-shared/audio/recorder";
import * as log from "tc-shared/log";
} from "tc-shared/audio/Recorder";
import {LogCategory, logDebug, logError} from "tc-shared/log";
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 {queryMediaPermissions} from "tc-shared/media/Stream";
import { tr } from "tc-shared/i18n/localize";
@ -49,7 +48,7 @@ class WebInputDeviceList extends AbstractDeviceList {
return "default";
}
getDevices(): IDevice[] {
getDevices(): InputDevice[] {
return this.devices;
}

View File

@ -1,7 +1,7 @@
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 {getAudioBackend} from "tc-shared/audio/Player";
import {SoundBackend, SoundFile} from "tc-shared/audio/Sounds";
interface SoundEntry {
cached?: AudioBuffer;
@ -13,7 +13,7 @@ const error_already_handled = "---- error handled ---";
const file_cache: {[key: string]: Promise<SoundEntry> & { timestamp: number }} = {};
let warned = false;
function get_song_entry(file: SoundFile) : Promise<SoundEntry> {
function getSongEntry(file: SoundFile) : Promise<SoundEntry> {
if(typeof file_cache[file.path] === "object") {
return new Promise<SoundEntry>((resolve, reject) => {
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)
return Promise.reject(error);
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");
return (file_cache[file.path] = Object.assign((async () => {
@ -78,8 +78,8 @@ function get_song_entry(file: SoundFile) : Promise<SoundEntry> {
})(), { timestamp: Date.now() }));
}
export async function play_sound(file: SoundFile) : Promise<void> {
const entry = get_song_entry(file);
async function replaySound(file: SoundFile) : Promise<void> {
const entry = getSongEntry(file);
if(!entry) {
logWarn(LogCategory.AUDIO, tr("Failed to replay sound %s because it could not be resolved."), file.path);
return;
@ -89,7 +89,7 @@ export async function play_sound(file: SoundFile) : Promise<void> {
const sound = await entry;
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!)");
const player = context.createBufferSource();
@ -127,3 +127,10 @@ export async function play_sound(file: SoundFile) : Promise<void> {
return;
}
}
export class WebSoundBackend implements SoundBackend {
playSound(sound: SoundFile): Promise<void> {
return replaySound(sound);
}
}

View File

@ -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();
}

View File

@ -10,8 +10,9 @@ function unescapeCommandValue(value: string) : string {
while (true) {
index = value.indexOf('\\', lastIndex);
if(index === -1 || index >= value.length + 1)
if(index === -1 || index >= value.length + 1) {
break;
}
let replace;
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));
let cmd;
if(parts[0][0].indexOf("=") === -1)
if(parts[0][0].indexOf("=") === -1) {
cmd = parts[0].pop_front();
}
let switches = [];
let payloads = [];
@ -72,10 +74,11 @@ export function parseCommand(command: string): ParsedCommand {
}
const separator = keyValue.indexOf('=');
if(separator === -1)
if(separator === -1) {
payload[keyValue] = "";
else
} else {
payload[keyValue.substring(0, separator)] = unescapeCommandValue(keyValue.substring(separator + 1));
}
}
payloads.push(payload)
@ -95,13 +98,15 @@ export function buildCommand(data: any | any[], switches?: string[], command?: s
result += " |";
for(const key of Object.keys(payload)) {
result += " " + key;
if(payload[key] !== undefined && payload[key] !== null)
if(payload[key] !== undefined && payload[key] !== null) {
result += " " + key + "=" + escapeCommandValue(payload[key].toString());
}
}
}
if(switches?.length)
if(switches?.length) {
result += " " + switches.map(e => "-" + e).join(" ");
}
return command ? command + result.substring(2) : result.substring(3);
}

View File

@ -13,11 +13,11 @@ import * as log from "tc-shared/log";
import {LogCategory, logDebug, logError, logInfo, logTrace, logWarn} from "tc-shared/log";
import {Regex} from "tc-shared/ui/modal/ModalConnect";
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 {parseCommand} from "tc-backend/web/connection/CommandParser";
import {parseCommand} from "./CommandParser";
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 {ServerFeature} from "tc-shared/connection/ServerFeatures";
import {RTCConnection} from "tc-shared/connection/rtc/Connection";

View File

@ -1,5 +1,4 @@
import {tr} from "tc-shared/i18n/localize";
import * as log from "tc-shared/log";
import {LogCategory, logError, logTrace} from "tc-shared/log";
export enum RRType {

View File

@ -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
});

View File

@ -1,4 +1,4 @@
import {setRecorderBackend} from "tc-shared/audio/recorder";
import {setRecorderBackend} from "tc-shared/audio/Recorder";
import {WebAudioRecorder} from "../audio/Recorder";
setRecorderBackend(new WebAudioRecorder());

View File

@ -1,7 +1,7 @@
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 {tr} from "tc-shared/i18n/localize";
import {resolveAddressIpV4, resolveTeaSpeakServerAddress} from "../dns/resolver";
setDNSProvider(new class implements DNSProvider {
resolveAddress(address: DNSAddress, options: DNSResolveOptions): Promise<DNSResolveResult> {

View File

@ -1,7 +1,7 @@
import * as loader from "tc-loader";
import {Stage} from "tc-loader";
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, {
priority: 50,

10
web/app/hooks/KeyBoard.ts Normal file
View File

@ -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
});

View File

@ -1,4 +1,4 @@
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());

View File

@ -1,9 +1,9 @@
import {ServerConnectionFactory, setServerConnectionFactory} from "tc-shared/connection/ConnectionFactory";
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
import {ServerConnection} from "tc-backend/web/connection/ServerConnection";
import * as loader from "tc-loader";
import {Stage} from "tc-loader";
import {ServerConnection} from "../connection/ServerConnection";
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
priority: 50,

10
web/app/hooks/Sounds.ts Normal file
View File

@ -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
});

9
web/app/hooks/index.ts Normal file
View File

@ -0,0 +1,9 @@
import "./AudioBackend";
import "./AudioRecorder";
import "./Backend";
import "./Dns";
import "./MenuBar";
import "./ServerConnection";
import "./Sounds";
import "./Video";
import "./KeyBoard";

View File

@ -4,12 +4,7 @@ import "webcrypto-liner";
import "./index.scss";
import "./FileTransfer";
import "./hooks/ServerConnection";
import "./hooks/ExternalModal";
import "./hooks/AudioRecorder";
import "./hooks/MenuBar";
import "./hooks/Video";
import "./hooks/Dns";
import "./hooks"
import "./UnloadHandler";

View File

@ -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];
}

View File

@ -1,7 +1,7 @@
import {MenuBarDriver, MenuBarEntry} from "tc-shared/ui/frames/menu-bar";
import * as React from "react";
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");

View File

@ -1,4 +1,3 @@
import * as aplayer from "../audio/player";
import {
AbstractVoiceConnection,
VoiceConnectionStatus,
@ -17,9 +16,10 @@ import {AbstractServerConnection, ConnectionStatistics} from "tc-shared/connecti
import {VoicePlayerState} from "tc-shared/voice/VoicePlayer";
import {LogCategory, logDebug, logError, logInfo, logTrace, logWarn} from "tc-shared/log";
import {tr} from "tc-shared/i18n/localize";
import {RtpVoiceClient} from "tc-backend/web/voice/VoiceClient";
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 };
export class RtpVoiceConnection extends AbstractVoiceConnection {
@ -86,8 +86,8 @@ export class RtpVoiceConnection extends AbstractVoiceConnection {
this.speakerMuted = connection.client.isSpeakerMuted() || connection.client.isSpeakerDisabled();
this.setConnectionState(VoiceConnectionStatus.Disconnected);
aplayer.on_ready(() => {
this.localAudioDestination = aplayer.context().createMediaStreamDestination();
getAudioBackend().executeWhenInitialized(() => {
this.localAudioDestination = getAudioBackend().getAudioContext().createMediaStreamDestination();
if(this.currentAudioSourceNode) {
this.currentAudioSourceNode.connect(this.localAudioDestination);
}

View File

@ -1,8 +1,5 @@
import {VoiceClient} from "tc-shared/voice/VoiceClient";
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 {
private readonly clientId: number;

View File

@ -1,6 +1,6 @@
import {VoicePlayerEvents, VoicePlayerLatencySettings, VoicePlayerState} from "tc-shared/voice/VoicePlayer";
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 {tr} from "tc-shared/i18n/localize";

View File

@ -9,8 +9,6 @@ export = env => config_base.config(env, "client").then(config => {
Object.assign(config.resolve.alias, {
"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))

View File

@ -9,8 +9,6 @@ export = env => config_base.config(env, "web").then(config => {
Object.assign(config.resolve.alias, {
"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);