@yume-chan/scrcpy-decoder-tinyh264
Version:
Raw H.264 stream decoder and renderer wrapping TinyH264 and YUVCanvas packages (software decoding).
77 lines • 2.39 kB
JavaScript
import { PromiseResolver } from "@yume-chan/async";
import { AutoDisposable, EventEmitter } from "@yume-chan/event";
let worker;
let workerReady = false;
const pendingResolvers = [];
let streamId = 0;
const PICTURE_READY_SUBSCRIPTIONS = new Map();
function subscribePictureReady(streamId, handler) {
PICTURE_READY_SUBSCRIPTIONS.set(streamId, handler);
return {
dispose() {
PICTURE_READY_SUBSCRIPTIONS.delete(streamId);
},
};
}
export class TinyH264Wrapper extends AutoDisposable {
streamId;
#pictureReadyEvent = new EventEmitter();
get onPictureReady() {
return this.#pictureReadyEvent.event;
}
constructor(streamId) {
super();
this.streamId = streamId;
this.addDisposable(subscribePictureReady(streamId, this.#handlePictureReady));
}
#handlePictureReady = (e) => {
this.#pictureReadyEvent.fire(e);
};
feed(data) {
worker.postMessage({
type: "decode",
data: data,
offset: 0,
length: data.byteLength,
renderStateId: this.streamId,
}, [data]);
}
dispose() {
super.dispose();
worker.postMessage({
type: "release",
renderStateId: this.streamId,
});
}
}
export function createTinyH264Wrapper() {
if (!worker) {
worker = new Worker(new URL("./worker.js", import.meta.url), {
type: "module",
});
worker.addEventListener("message", ({ data }) => {
switch (data.type) {
case "decoderReady":
workerReady = true;
for (const resolver of pendingResolvers) {
resolver.resolve(new TinyH264Wrapper(streamId));
streamId += 1;
}
pendingResolvers.length = 0;
break;
case "pictureReady":
PICTURE_READY_SUBSCRIPTIONS.get(data.renderStateId)?.(data);
break;
}
});
}
if (!workerReady) {
const resolver = new PromiseResolver();
pendingResolvers.push(resolver);
return resolver.promise;
}
const decoder = new TinyH264Wrapper(streamId);
streamId += 1;
return Promise.resolve(decoder);
}
//# sourceMappingURL=wrapper.js.map