canary
WolverinDEV 2018-04-18 20:12:10 +02:00
parent cf321654dc
commit 02e7f8c355
21 changed files with 185 additions and 279789 deletions

2
.gitignore vendored
View File

@ -0,0 +1,2 @@
js/**/*.js*
.idea/

File diff suppressed because one or more lines are too long

View File

@ -4189,7 +4189,7 @@ class TSClient {
}
else {
host = addr;
port = 19974;
port = 9987;
}
console.log("Start connection to " + host + ":" + port);
this.channelTree.initialiseHead(addr);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -20,7 +20,8 @@ abstract class BasicCodec implements Codec {
}
abstract name() : string;
abstract initialise();
abstract initialise() : Promise<Boolean>;
abstract initialized() : boolean;
abstract deinitialise();
abstract reset() : boolean;

View File

@ -11,17 +11,43 @@ class CodecWrapper extends BasicCodec {
private _workerTokeIndex: number = 0;
type: CodecWorkerType;
private _initialized: boolean = false;
private _workerCallbackResolve: () => any;
private _workerCallbackReject: ($: any) => any;
private _initializePromise: Promise<Boolean>;
name(): string {
return "Worker for " + CodecWorkerType[this.type] + " Channels " + this.channelCount;
}
initialise() {
this.spawnWorker();
initialise() : Promise<Boolean> {
if(this._initializePromise) return this._initializePromise;
console.log("INIT!");
return this._initializePromise = this.spawnWorker().then(() => new Promise<Boolean>((resolve, reject) => {
const token = this.generateToken();
this.sendWorkerMessage({
command: "initialise",
type: this.type,
channelCount: this.channelCount
channelCount: this.channelCount,
token: token
});
this._workerListener.push({
token: token,
resolve: data => {
console.log("Init result: %o", data);
this._initialized = data["success"] == true;
if(data["success"] == true)
resolve();
else
reject(data.message);
}
})
}));
}
initialized() : boolean {
return this._initialized;
}
deinitialise() {
@ -31,7 +57,7 @@ class CodecWrapper extends BasicCodec {
}
decode(data: Uint8Array): Promise<AudioBuffer> {
let token = this._workerTokeIndex++ + "_token";
let token = this.generateToken();
let result = new Promise<AudioBuffer>((resolve, reject) => {
this._workerListener.push(
{
@ -64,7 +90,8 @@ class CodecWrapper extends BasicCodec {
}
encode(data: AudioBuffer) : Promise<Uint8Array> {
let token = this._workerTokeIndex++ + "_token";
console.log(data);
let token = this.generateToken();
let result = new Promise<Uint8Array>((resolve, reject) => {
this._workerListener.push(
{
@ -112,6 +139,10 @@ class CodecWrapper extends BasicCodec {
this.channelCount = channelCount;
}
private generateToken() {
return this._workerTokeIndex++ + "_token";
}
private sendWorkerMessage(message: any, transfare?: any[]) {
this._worker.postMessage(JSON.stringify(message), transfare);
}
@ -123,6 +154,17 @@ class CodecWrapper extends BasicCodec {
}
if(message["token"] == this._workerCallbackToken) {
if(message["type"] == "loaded") {
console.log("Got loaded result: %o", message);
if(message["success"]) {
if(this._workerCallbackResolve)
this._workerCallbackResolve();
} else {
if(this._workerCallbackReject)
this._workerCallbackReject(message["message"]);
}
console.log("Worker initialized!");
}
console.log("Callback data!");
return;
}
@ -138,8 +180,12 @@ class CodecWrapper extends BasicCodec {
console.error("Could not find worker token entry! (" + message["token"] + ")");
}
private spawnWorker() {
this._worker = new Worker("js/codec/workers/CompiledCodecWorker.js");
private spawnWorker() : Promise<Boolean> {
return new Promise<Boolean>((resolve, reject) => {
this._workerCallbackReject = reject;
this._workerCallbackResolve = resolve;
this._worker = new Worker(settings.static("worker_directory", "js/workers/") + "WorkerCodec.js");
this._worker.onmessage = event => this.onWorkerMessage(JSON.parse(event.data));
});
}
}

View File

@ -13,9 +13,14 @@ class RawCodec extends BasicCodec {
return "raw";
}
initialise() {
initialise() : Promise<Boolean> {
this.converterRaw = Module._malloc(this.bufferSize);
this.converter = new Uint8Array(Module.HEAPU8.buffer, this.converterRaw, this.bufferSize);
return new Promise<Boolean>(resolve => resolve());
}
initialized(): boolean {
return true;
}
deinitialise() { }

View File

@ -1,2 +0,0 @@
#!/usr/bin/env bash
/usr/local/bin/tsc

View File

@ -1,5 +1,8 @@
//Source: https://www.movable-type.co.uk/scripts/sha1.html
declare class TextEncoder {
encode(msg) : ArrayBuffer;
}
namespace sha {
export function sha1(message: string | ArrayBuffer) : PromiseLike<ArrayBuffer> {
let buffer = message instanceof ArrayBuffer ? message : new TextEncoder().encode(message);

View File

@ -181,13 +181,14 @@ function loadTemplates() {
});
}
//TODO release config!
function loadSide() {
//Load the general scripts and required scripts
awaitLoad(loadScripts([
["vendor/jquery/jquery.min.js", /*"https://code.jquery.com/jquery-latest.min.js"*/],
["https://webrtc.github.io/adapter/adapter-latest.js"]
])).then(() => awaitLoad(loadScripts([
["asm/generated/TeaWeb-Native.js", "js/TeaWeb-Native.js"],
["asm/generated/TeaWeb-Identity.js", "js/TeaWeb-Native.js"],
["https://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"]
]))).then(() => {
//Load the teaweb scripts

View File

@ -14,8 +14,14 @@ let chat: ChatBox;
let forumIdentity: TeaForumIdentity;
function invokeMain() {
Module['onRuntimeInitialized'] = function() {
console.log("Runtime ready!");
main();
};
}
function main() {
//localhost:63343/Web-Client/index.php?disableUnloadDialog=1&default_connect_type=forum&default_connect_url=localhost
AudioController.initializeAudioController();
if(!TSIdentityHelper.setup()) { console.error("Could not setup the TeamSpeak identity parser!"); return; }

View File

@ -44,11 +44,12 @@ class Settings {
});
}
private static transformStO?<T>(input: string, _default?: T) : T {
private static transformStO?<T>(input?: string, _default?: T) : T {
if (typeof input === "undefined") return _default;
if (typeof _default === "string") return input as any;
else if (typeof _default === "number") return parseInt(input) as any;
else if (typeof _default === "boolean") return (input == "1" || input == "true") as any;
else if (typeof _default == "undefined") return input as any;
else if (typeof _default === "undefined") return input as any;
return JSON.parse(input) as any;
}
@ -72,6 +73,7 @@ class Settings {
static?<T>(key: string, _default?: T) : T {
let result = this._staticPropsTag.find("[key='" + key + "']");
console.log("%d | %o", result.length, result);
return Settings.transformStO(result.length > 0 ? decodeURIComponent(result.last().attr("value")) : undefined, _default);
}

View File

@ -105,6 +105,7 @@ class AudioController {
if (buffer.sampleRate != this.speakerContext.sampleRate)
console.warn("[AudioController] Source sample rate isn't equal to playback sample rate! (" + buffer.sampleRate + " | " + this.speakerContext.sampleRate + ")");
console.log("%o", buffer);
this.applayVolume(buffer);
this.audioCache.push(buffer);
if(this.playerState == PlayerState.STOPPED || this.playerState == PlayerState.STOPPING) {

View File

@ -12,7 +12,7 @@ class AudioResampler {
return new Promise<AudioBuffer>(resolve => resolve(buffer));
let context;
context = new OfflineAudioContext(buffer.numberOfChannels, Math.floor(buffer.length * this.targetSampleRate / buffer.sampleRate), this.targetSampleRate);
context = new OfflineAudioContext(buffer.numberOfChannels, Math.ceil(buffer.length * this.targetSampleRate / buffer.sampleRate), this.targetSampleRate);
let source = context.createBufferSource();
source.buffer = buffer;

View File

@ -7,6 +7,8 @@ class CodecPoolEntry {
owner: number;
last_access: number;
private _initializePromise: Promise<Boolean>;
}
class CodecPool {
@ -19,39 +21,63 @@ class CodecPool {
initialize(cached: number) {
for(let i = 0; i < cached; i++)
this.ownCodec(i);
for(let i = 0; i < cached; i++)
this.releaseCodec(i);
this.ownCodec(i + 1).then(codec => {
console.log("Release again! (%o)", codec);
this.releaseCodec(i + 1);
}).catch(errror => {
console.error(errror);
});
}
supported() { return this.creator != undefined; }
ownCodec?(clientId: number, create: boolean = true) : BasicCodec {
if(!this.creator) return null;
ownCodec?(clientId: number, create: boolean = true) : Promise<BasicCodec | undefined> {
return new Promise<BasicCodec>((resolve, reject) => {
if(!this.creator) {
reject("unsupported codec!");
return;
}
let free = 0;
let freeSlot = 0;
for(let index = 0; index < this.entries.length; index++) {
if(this.entries[index].owner == clientId) {
this.entries[index].last_access = new Date().getTime();
return this.entries[index].instance;
} else if(free == 0 && this.entries[index].owner == 0) {
free = index;
if(this.entries[index].instance.initialized()) resolve(this.entries[index].instance);
else resolve(this.entries[index].instance.initialise().catch(error => {
console.error("Could not initialize codec!\nError: %o", error);
reject("Could not initialize codec!");
}).then((flag) => {
//TODO test success flag
return this.ownCodec(clientId, false);
}));
return;
} else if(freeSlot == 0 && this.entries[index].owner == 0) {
freeSlot = index;
}
}
if(!create) return null;
if(free == 0){
free = this.entries.length;
if(!create) {
resolve(undefined);
return;
}
if(freeSlot == 0){
freeSlot = this.entries.length;
let entry = new CodecPoolEntry();
entry.instance = this.creator();
entry.instance.initialise();
entry.instance.on_encoded_data = buffer => this.handle.sendVoicePacket(buffer, this.codecIndex);
this.entries.push(entry);
}
this.entries[free].owner = clientId;
this.entries[free].last_access = new Date().getTime();
this.entries[free].instance.reset();
return this.entries[free].instance;
this.entries[freeSlot].owner = clientId;
this.entries[freeSlot].last_access = new Date().getTime();
if(this.entries[freeSlot].instance.initialized())
this.entries[freeSlot].instance.reset();
else {
resolve(this.ownCodec(clientId, false));
return;
}
resolve(this.entries[freeSlot].instance);
});
}
releaseCodec(clientId: number) {
@ -210,12 +236,18 @@ class VoiceConnection {
client.getAudioController().stopAudio();
codecPool.releaseCodec(clientId);
} else {
codecPool.ownCodec(clientId)
.then(decoder => decoder.decodeSamples(client.getAudioController().codecCache(codec), encodedData))
.then(buffer => client.getAudioController().playBuffer(buffer)).catch(error => {
console.error("Could not playback client's (" + clientId + ") audio (" + error + ")");
});
/*
let decoder = codecPool.ownCodec(clientId);
decoder.decodeSamples(client.getAudioController().codecCache(codec), encodedData).then(buffer => {
client.getAudioController().playBuffer(buffer);
}).catch(error => {
console.error("Could not playback client's (" + clientId + ") audio (" + error + ")");
});
*/
}
}
@ -228,12 +260,10 @@ class VoiceConnection {
this.chunkVPacketId = 0;
this.client.getClient().speaking = true;
}
let encoder = this.codecPool[4].ownCodec(this.client.getClientId());
if(!encoder) {
console.error("Could not reserve encoder!");
return;
}
encoder.encodeSamples(this.client.getClient().getAudioController().codecCache(4),data); //TODO Use channel codec!
//TODO Use channel codec!
this.codecPool[4].ownCodec(this.client.getClientId())
.then(encoder => encoder.encodeSamples(this.client.getClient().getAudioController().codecCache(4),data));
//this.client.getClient().getAudioController().play(data);
}

View File

@ -1,4 +1,6 @@
const prefix = "[CodecWorker] ";
const workerCallbackToken = "callback_token";
interface CodecWorker {
name();
initialise();
@ -12,14 +14,15 @@ interface CodecWorker {
enum CodecWorkerType {
WORKER_OPUS
}
let codecInstance: CodecWorker;
onmessage = function(e) {
let data = JSON.parse(e.data);
//console.log(data);
let res: any = {};
res.token = data.token;
res.success = false;
switch (data.command) {
case "initialise":
@ -30,13 +33,14 @@ onmessage = function(e) {
break;
default:
res.message = "Could not find worker type!";
console.error("Could not resolve opus type!");
return;
}
codecInstance.initialise();
res["success"] = true;
break;
case "encodeSamples":
let encodeArray = new Float32Array(data.dataLength);
for(let index = 0; index < encodeArray.length; index++)
@ -45,16 +49,13 @@ onmessage = function(e) {
let encodeResult = codecInstance.encode(encodeArray);
if(typeof encodeResult === "string") {
res.success = false;
res.message = encodeResult;
} else {
res.success = true;
res.data = encodeResult;
res.dataLength = encodeResult.length;
}
sendMessage(res, e.origin);
break;
case "decodeSamples":
let decodeArray = new Uint8Array(data.dataLength);
for(let index = 0; index < decodeArray.length; index++)
@ -63,26 +64,24 @@ onmessage = function(e) {
let decodeResult = codecInstance.decode(decodeArray);
if(typeof decodeResult === "string") {
res.success = false;
res.message = decodeResult;
} else {
res.success = true;
res.data = decodeResult;
res.dataLength = decodeResult.length;
}
sendMessage(res, e.origin);
break;
case "reset":
codecInstance.reset();
break;
default:
console.error(prefix + "Unknown type " + data.command);
}
if(res.token && res.token.length > 0) sendMessage(res, e.origin);
};
declare function postMessage(message: any): void;
function sendMessage(message: any, origin: string){
function sendMessage(message: any, origin?: string){
postMessage(JSON.stringify(message));
}

View File

@ -1,20 +1,26 @@
/// <reference path="CodecWorker.ts" />
//"js/TeaWeb-Native.js", "asm/generated/TeaWeb-Native.js"
this["Module"] = typeof this["Module"] !== "undefined" ? this["Module"] : {};
let initialized = false;
Module['onRuntimeInitialized'] = function() {
initialized = true;
console.log(prefix + "Initialized!");
sendMessage({
token: workerCallbackToken,
type: "loaded",
success: true
})
};
//let Module = typeof Module !== 'undefined' ? Module : {};
try {
importScripts("../TeaWeb-Native.js");
} catch (e) {
try {
importScripts("../../asm/generated/TeaWeb-Native.js");
} catch (e) {
try {
importScripts("../../../asm/generated/TeaWeb-Native.js");
Module['locateFile'] = file => "../../asm/generated/" + file;
importScripts("../../asm/generated/TeaWeb-Worker-Codec-Opus.js");
} catch (e) {
console.error("Could not load native script!");
console.log(e);
}
}
}
enum OpusType {
VOIP = 2048,
@ -89,6 +95,7 @@ class OpusWorker implements CodecWorker {
}
reset() {
console.log(prefix + " Reseting opus codec!");
this.fn_reset(this.nativeHandle);
}
}

2
js/workers/compile.sh Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
/usr/local/bin/tsc -p tsconfig_worker_codec.json

View File

@ -6,6 +6,6 @@
},
"exclude": [
"node_modules",
"js/codec/workers"
"js/workers"
]
}