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 { else {
host = addr; host = addr;
port = 19974; port = 9987;
} }
console.log("Start connection to " + host + ":" + port); console.log("Start connection to " + host + ":" + port);
this.channelTree.initialiseHead(addr); 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 name() : string;
abstract initialise(); abstract initialise() : Promise<Boolean>;
abstract initialized() : boolean;
abstract deinitialise(); abstract deinitialise();
abstract reset() : boolean; abstract reset() : boolean;

View File

@ -11,17 +11,43 @@ class CodecWrapper extends BasicCodec {
private _workerTokeIndex: number = 0; private _workerTokeIndex: number = 0;
type: CodecWorkerType; type: CodecWorkerType;
private _initialized: boolean = false;
private _workerCallbackResolve: () => any;
private _workerCallbackReject: ($: any) => any;
private _initializePromise: Promise<Boolean>;
name(): string { name(): string {
return "Worker for " + CodecWorkerType[this.type] + " Channels " + this.channelCount; return "Worker for " + CodecWorkerType[this.type] + " Channels " + this.channelCount;
} }
initialise() { initialise() : Promise<Boolean> {
this.spawnWorker(); if(this._initializePromise) return this._initializePromise;
this.sendWorkerMessage({ console.log("INIT!");
command: "initialise", return this._initializePromise = this.spawnWorker().then(() => new Promise<Boolean>((resolve, reject) => {
type: this.type, const token = this.generateToken();
channelCount: this.channelCount this.sendWorkerMessage({
}); command: "initialise",
type: this.type,
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() { deinitialise() {
@ -31,7 +57,7 @@ class CodecWrapper extends BasicCodec {
} }
decode(data: Uint8Array): Promise<AudioBuffer> { decode(data: Uint8Array): Promise<AudioBuffer> {
let token = this._workerTokeIndex++ + "_token"; let token = this.generateToken();
let result = new Promise<AudioBuffer>((resolve, reject) => { let result = new Promise<AudioBuffer>((resolve, reject) => {
this._workerListener.push( this._workerListener.push(
{ {
@ -64,7 +90,8 @@ class CodecWrapper extends BasicCodec {
} }
encode(data: AudioBuffer) : Promise<Uint8Array> { encode(data: AudioBuffer) : Promise<Uint8Array> {
let token = this._workerTokeIndex++ + "_token"; console.log(data);
let token = this.generateToken();
let result = new Promise<Uint8Array>((resolve, reject) => { let result = new Promise<Uint8Array>((resolve, reject) => {
this._workerListener.push( this._workerListener.push(
{ {
@ -112,6 +139,10 @@ class CodecWrapper extends BasicCodec {
this.channelCount = channelCount; this.channelCount = channelCount;
} }
private generateToken() {
return this._workerTokeIndex++ + "_token";
}
private sendWorkerMessage(message: any, transfare?: any[]) { private sendWorkerMessage(message: any, transfare?: any[]) {
this._worker.postMessage(JSON.stringify(message), transfare); this._worker.postMessage(JSON.stringify(message), transfare);
} }
@ -123,6 +154,17 @@ class CodecWrapper extends BasicCodec {
} }
if(message["token"] == this._workerCallbackToken) { 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!"); console.log("Callback data!");
return; return;
} }
@ -138,8 +180,12 @@ class CodecWrapper extends BasicCodec {
console.error("Could not find worker token entry! (" + message["token"] + ")"); console.error("Could not find worker token entry! (" + message["token"] + ")");
} }
private spawnWorker() { private spawnWorker() : Promise<Boolean> {
this._worker = new Worker("js/codec/workers/CompiledCodecWorker.js"); return new Promise<Boolean>((resolve, reject) => {
this._worker.onmessage = event => this.onWorkerMessage(JSON.parse(event.data)); 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"; return "raw";
} }
initialise() { initialise() : Promise<Boolean> {
this.converterRaw = Module._malloc(this.bufferSize); this.converterRaw = Module._malloc(this.bufferSize);
this.converter = new Uint8Array(Module.HEAPU8.buffer, this.converterRaw, this.bufferSize); this.converter = new Uint8Array(Module.HEAPU8.buffer, this.converterRaw, this.bufferSize);
return new Promise<Boolean>(resolve => resolve());
}
initialized(): boolean {
return true;
} }
deinitialise() { } 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 //Source: https://www.movable-type.co.uk/scripts/sha1.html
declare class TextEncoder {
encode(msg) : ArrayBuffer;
}
namespace sha { namespace sha {
export function sha1(message: string | ArrayBuffer) : PromiseLike<ArrayBuffer> { export function sha1(message: string | ArrayBuffer) : PromiseLike<ArrayBuffer> {
let buffer = message instanceof ArrayBuffer ? message : new TextEncoder().encode(message); let buffer = message instanceof ArrayBuffer ? message : new TextEncoder().encode(message);

View File

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

View File

@ -14,8 +14,14 @@ let chat: ChatBox;
let forumIdentity: TeaForumIdentity; let forumIdentity: TeaForumIdentity;
function invokeMain() { 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 //localhost:63343/Web-Client/index.php?disableUnloadDialog=1&default_connect_type=forum&default_connect_url=localhost
AudioController.initializeAudioController(); AudioController.initializeAudioController();
if(!TSIdentityHelper.setup()) { console.error("Could not setup the TeamSpeak identity parser!"); return; } 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; if (typeof _default === "string") return input as any;
else if (typeof _default === "number") return parseInt(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 === "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; return JSON.parse(input) as any;
} }
@ -72,6 +73,7 @@ class Settings {
static?<T>(key: string, _default?: T) : T { static?<T>(key: string, _default?: T) : T {
let result = this._staticPropsTag.find("[key='" + key + "']"); 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); 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) 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.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.applayVolume(buffer);
this.audioCache.push(buffer); this.audioCache.push(buffer);
if(this.playerState == PlayerState.STOPPED || this.playerState == PlayerState.STOPPING) { if(this.playerState == PlayerState.STOPPED || this.playerState == PlayerState.STOPPING) {

View File

@ -12,7 +12,7 @@ class AudioResampler {
return new Promise<AudioBuffer>(resolve => resolve(buffer)); return new Promise<AudioBuffer>(resolve => resolve(buffer));
let context; 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(); let source = context.createBufferSource();
source.buffer = buffer; source.buffer = buffer;

View File

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

View File

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

View File

@ -1,19 +1,25 @@
/// <reference path="CodecWorker.ts" /> /// <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 { try {
importScripts("../TeaWeb-Native.js"); Module['locateFile'] = file => "../../asm/generated/" + file;
importScripts("../../asm/generated/TeaWeb-Worker-Codec-Opus.js");
} catch (e) { } catch (e) {
try { console.error("Could not load native script!");
importScripts("../../asm/generated/TeaWeb-Native.js"); console.log(e);
} catch (e) {
try {
importScripts("../../../asm/generated/TeaWeb-Native.js");
} catch (e) {
console.error("Could not load native script!");
console.log(e);
}
}
} }
enum OpusType { enum OpusType {
@ -89,6 +95,7 @@ class OpusWorker implements CodecWorker {
} }
reset() { reset() {
console.log(prefix + " Reseting opus codec!");
this.fn_reset(this.nativeHandle); 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": [ "exclude": [
"node_modules", "node_modules",
"js/codec/workers" "js/workers"
] ]
} }