Files
2025-12-09 20:22:03 +08:00

178 lines
4.4 KiB
JavaScript

const WsClientOptions = {
// required on open
url: null,
stream: null,
decode_async: false,
decode_queue_size: 0,
decode_thread_count: 0,
decode_thread_type: 0,
// need pthreads support
// COOP, COEP headers are correctly set
// https://emscripten.org/docs/porting/pthreads.html
// decode_async: false, // not works well as clone frame not supported
// decode_queue_size: 10,
// decode_thread_count: 4,
// // 1: FF_THREAD_FRAME, 2: FF_THREAD_SLICE
// decode_thread_type: 1,
player: null,
onopen: null,
onmessage: null,
onclose: null,
onerror: null,
ondata: null,
dbg: false,
log: console.log,
wasm_log_v: 0,
};
class WsClient {
#options;
#ws = null;
#decoder = null;
#t_onmsg = Date.now();
constructor(options) {
this.#options = { ...WsClientOptions, ...options };
this.#logd('ws options:')
this.#logd(this.#options);
Module.Log.set_v(this.#options.wasm_log_v);
}
static createOpenGLPlayer() {
return new Module.OpenGLPlayer();
}
#log(...args) {
this.#options.log(...args);
}
#logd(...args) {
this.#options.dbg && this.#options.log(...args);
}
isOpen() {
return this.#ws != null;
}
open(options) {
if (this.#ws != null) {
this.#log('ws open error: already opened');
return;
}
this.#options = { ...this.#options, ...options };
this.#logd('ws open options:')
this.#logd(this.#options);
if (this.#options.url == null) {
this.#log('ws open error: url is null');
return;
}
if (this.#options.stream == null) {
this.#log('ws open error: stream is null');
return;
}
this.#decoder = new Module.Decoder();
this.#decoder.open(
JSON.stringify(this.#options.stream),
this.#options.decode_queue_size,
this.#options.decode_thread_count,
this.#options.decode_thread_type,
this.#ondecode.bind(this));
const ws = new WebSocket(this.#options.url);
ws.binaryType = 'arraybuffer';
ws.onopen = (e) => this.#onopen(e);
ws.onmessage = (e) => this.#onmessage(e);
ws.onclose = (e) => this.#onclose(e);
ws.onerror = (e) => this.#onerror(e);
this.#ws = ws;
}
close() {
if (this.#ws != null) {
this.#logd('ws close');
this.#ws.close();
this.#ws = null;
this.#decoder.delete();
this.#decoder = null;
this.#options.dbg && Module.DoLeakCheck && Module.DoLeakCheck();
}
}
#onopen(e) {
this.#logd(`ws open: ${this.#options.url}`);
this.#options.onopen && this.#options.onopen(e);
}
#onmessage(e) {
if (this.#options.dbg) {
const t = Date.now();
this.#logd(`ws message: ${this.#options.url}` +
`, interval: ${t - this.#t_onmsg} ms`);
this.#t_onmsg = t;
}
this.#options.dbg && console.time("ws onmessage");
console.log('onmessage=',e);
let data = new Uint8Array(e.data);
if (this.#decoder) {
const buf = Module._malloc(data.length);
try {
Module.HEAPU8.set(data, buf);
if (this.#options.decode_async) {
this.#decoder.decodeAsync(buf, data.length);
} else {
// process the frame in #ondecode callback
const frame = this.#decoder.decode(buf, data.length);
// release the return frame reference
if (frame != null) frame.delete();
}
} finally {
Module._free(buf);
}
} else {
this.#options.ondata && this.#options.ondata(data);
}
this.#options.dbg && console.timeEnd("ws onmessage");
}
#ondecode(frame) {
this.#options.dbg && console.time("ws ondecode");
if (frame != null) {
this.#logd(`ws frame size=${frame.width}x${frame.height}, pts=${frame.pts}`);
const player = this.#options.player;
if (player) {
if (player instanceof WebGLPlayer) {
frame.bytes = frame.getBytes();
// frame.bytes = new Uint8Array(Module.HEAPU8.buffer, frame.data, frame.size);
}
player.render(frame);
}
this.#options.ondata && this.#options.ondata(frame);
frame.delete();
} else {
this.#logd("ws frame is null: decode error or need new packets");
}
this.#options.dbg && console.timeEnd("ws ondecode");
}
#onclose(e) {
this.#logd(`ws close: ${this.#options.url}`);
this.#options.onclose && this.#options.onclose(e);
}
#onerror(e) {
this.#logd(`ws error: ${this.#options.url}, ${e}`);
this.#options.onerror && this.#options.onerror(e);
}
}