TeaWeb/web/js/codec/CodecWrapperWorker.ts

210 lines
7.5 KiB
TypeScript
Raw Normal View History

2020-03-30 13:44:18 +02:00
import {BasicCodec} from "./BasicCodec";
import {CodecType} from "./Codec";
import {LogCategory} from "tc-shared/log";
import * as log from "tc-shared/log";
import {settings} from "tc-shared/settings";
2018-04-11 17:56:09 +02:00
2020-03-30 13:44:18 +02:00
export class CodecWrapperWorker extends BasicCodec {
2018-04-11 17:56:09 +02:00
private _worker: Worker;
private _workerListener: {token: string, resolve: (data: any) => void}[] = [];
private _workerCallbackToken = "callback_token";
private _workerTokeIndex: number = 0;
2018-11-12 13:00:13 +01:00
type: CodecType;
2018-04-11 17:56:09 +02:00
2018-04-18 20:12:10 +02:00
private _initialized: boolean = false;
private _workerCallbackResolve: () => any;
private _workerCallbackReject: ($: any) => any;
private _initializePromise: Promise<Boolean>;
2018-04-11 17:56:09 +02:00
name(): string {
2018-11-12 13:00:13 +01:00
return "Worker for " + CodecType[this.type] + " Channels " + this.channelCount;
2018-04-11 17:56:09 +02:00
}
2018-04-18 20:12:10 +02:00
initialise() : Promise<Boolean> {
if(this._initializePromise) return this._initializePromise;
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,
token: token
});
this._workerListener.push({
token: token,
resolve: data => {
this._initialized = data["success"] == true;
if(data["success"] == true)
resolve();
else
reject(data.message);
}
})
}));
}
initialized() : boolean {
return this._initialized;
2018-04-11 17:56:09 +02:00
}
deinitialise() {
this.sendWorkerMessage({
command: "deinitialise"
});
}
decode(data: Uint8Array): Promise<AudioBuffer> {
2018-04-18 20:12:10 +02:00
let token = this.generateToken();
2018-04-11 17:56:09 +02:00
let result = new Promise<AudioBuffer>((resolve, reject) => {
this._workerListener.push(
{
token: token,
resolve: (data) => {
if(data.success) {
let array = new Float32Array(data.dataLength);
for(let index = 0; index < array.length; index++)
array[index] = data.data[index];
let audioBuf = this._audioContext.createBuffer(this.channelCount, array.length / this.channelCount, this._codecSampleRate);
2018-10-16 19:46:30 +02:00
for (let channel = 0; channel < this.channelCount; channel++) {
for (let offset = 0; offset < audioBuf.length; offset++) {
2018-11-04 15:41:50 +01:00
audioBuf.getChannelData(channel)[offset] = array[channel + offset * this.channelCount];
2018-10-16 19:46:30 +02:00
}
}
2018-04-11 17:56:09 +02:00
resolve(audioBuf);
} else {
reject(data.message);
}
}
}
);
});
this.sendWorkerMessage({
command: "decodeSamples",
token: token,
data: data,
dataLength: data.length
});
return result;
}
encode(data: AudioBuffer) : Promise<Uint8Array> {
2018-04-18 20:12:10 +02:00
let token = this.generateToken();
2018-04-11 17:56:09 +02:00
let result = new Promise<Uint8Array>((resolve, reject) => {
this._workerListener.push(
{
token: token,
resolve: (data) => {
if(data.success) {
let array = new Uint8Array(data.dataLength);
for(let index = 0; index < array.length; index++)
array[index] = data.data[index];
resolve(array);
} else {
reject(data.message);
}
}
}
);
});
let buffer = new Float32Array(this.channelCount * data.length);
for (let offset = 0; offset < data.length; offset++) {
for (let channel = 0; channel < this.channelCount; channel++)
buffer[offset * this.channelCount + channel] = data.getChannelData(channel)[offset];
}
this.sendWorkerMessage({
command: "encodeSamples",
token: token,
data: buffer,
dataLength: buffer.length
});
return result;
}
reset() : boolean {
this.sendWorkerMessage({
command: "reset"
});
return true;
}
2018-11-12 13:00:13 +01:00
constructor(type: CodecType) {
2018-04-11 17:56:09 +02:00
super(48000);
this.type = type;
2018-11-12 13:00:13 +01:00
switch (type) {
case CodecType.OPUS_MUSIC:
this.channelCount = 2;
break;
case CodecType.OPUS_VOICE:
this.channelCount = 1;
break;
default:
throw "invalid codec type!";
}
2018-04-11 17:56:09 +02:00
}
2018-04-18 20:12:10 +02:00
private generateToken() {
return this._workerTokeIndex++ + "_token";
}
2018-04-11 17:56:09 +02:00
private sendWorkerMessage(message: any, transfare?: any[]) {
2018-05-07 11:51:50 +02:00
message["timestamp"] = Date.now();
this._worker.postMessage(message, transfare as any);
2018-04-11 17:56:09 +02:00
}
private onWorkerMessage(message: any) {
if(!message["token"]) {
2019-08-30 23:06:39 +02:00
log.error(LogCategory.VOICE, tr("Invalid worker token!"));
2018-04-11 17:56:09 +02:00
return;
}
if(message["token"] == this._workerCallbackToken) {
2018-04-18 20:12:10 +02:00
if(message["type"] == "loaded") {
2019-08-30 23:06:39 +02:00
log.info(LogCategory.VOICE, tr("[Codec] Got worker init response: Success: %o Message: %o"), message["success"], message["message"]);
2018-04-18 20:12:10 +02:00
if(message["success"]) {
if(this._workerCallbackResolve)
this._workerCallbackResolve();
} else {
if(this._workerCallbackReject)
this._workerCallbackReject(message["message"]);
}
2018-04-19 18:42:34 +02:00
this._workerCallbackReject = undefined;
this._workerCallbackResolve = undefined;
return;
2018-05-07 11:51:50 +02:00
} else if(message["type"] == "chatmessage_server") {
2019-04-04 21:47:52 +02:00
//FIXME?
2018-05-07 11:51:50 +02:00
return;
2018-04-18 20:12:10 +02:00
}
2019-08-30 23:06:39 +02:00
log.debug(LogCategory.VOICE, tr("Costume callback! (%o)"), message);
2018-04-11 17:56:09 +02:00
return;
}
2019-08-30 23:06:39 +02:00
/* lets warn on general packets. Control packets are allowed to "stuck" a bit longer */
if(Date.now() - message["timestamp"] > 5)
log.warn(LogCategory.VOICE, tr("Worker message stock time: %d"), Date.now() - message["timestamp"]);
2018-04-11 17:56:09 +02:00
for(let entry of this._workerListener) {
if(entry.token == message["token"]) {
entry.resolve(message);
this._workerListener.remove(entry);
return;
}
}
2019-08-30 23:06:39 +02:00
log.error(LogCategory.VOICE, tr("Could not find worker token entry! (%o)"), message["token"]);
2018-04-11 17:56:09 +02:00
}
2018-04-18 20:12:10 +02:00
private spawnWorker() : Promise<Boolean> {
return new Promise<Boolean>((resolve, reject) => {
this._workerCallbackReject = reject;
this._workerCallbackResolve = resolve;
2018-04-19 18:42:34 +02:00
2018-04-18 20:12:10 +02:00
this._worker = new Worker(settings.static("worker_directory", "js/workers/") + "WorkerCodec.js");
this._worker.onmessage = event => this.onWorkerMessage(event.data);
this._worker.onerror = (error: ErrorEvent) => reject("Failed to load worker (" + error.message + ")"); //TODO tr
2018-04-18 20:12:10 +02:00
});
2018-04-11 17:56:09 +02:00
}
}