@muhkoo/theater-transcoder 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -2
- package/src/client.js +12 -1
- package/src/worker.js +12 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muhkoo/theater-transcoder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Drain a Muhkoo Theater transcode queue with NATIVE ffmpeg. Run it on any machine signed in as you to add transcoding power to your library.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,13 +14,20 @@
|
|
|
14
14
|
"node": ">=20"
|
|
15
15
|
},
|
|
16
16
|
"license": "MIT",
|
|
17
|
-
"keywords": [
|
|
17
|
+
"keywords": [
|
|
18
|
+
"muhkoo",
|
|
19
|
+
"transcode",
|
|
20
|
+
"ffmpeg",
|
|
21
|
+
"hls",
|
|
22
|
+
"worker"
|
|
23
|
+
],
|
|
18
24
|
"publishConfig": {
|
|
19
25
|
"access": "public"
|
|
20
26
|
},
|
|
21
27
|
"dependencies": {
|
|
22
28
|
"@muhkoo/connect": "0.10.1-alpha.0",
|
|
23
29
|
"ffmpeg-static": "^5.2.0",
|
|
30
|
+
"node-datachannel": "^0.12.0",
|
|
24
31
|
"snarkjs": "^0.7.5",
|
|
25
32
|
"ws": "^8.18.0"
|
|
26
33
|
}
|
package/src/client.js
CHANGED
|
@@ -11,11 +11,19 @@ import { mkdtemp, writeFile } from "node:fs/promises";
|
|
|
11
11
|
import { tmpdir } from "node:os";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { WebSocket } from "ws";
|
|
14
|
+
import * as ndc from "node-datachannel/polyfill";
|
|
14
15
|
import { Client } from "@muhkoo/connect";
|
|
15
16
|
|
|
16
17
|
// Node 20's global WebSocket is flag-gated; ensure the Space socket has one.
|
|
17
18
|
if (!globalThis.WebSocket) globalThis.WebSocket = WebSocket;
|
|
18
19
|
|
|
20
|
+
// WebRTC polyfill so connect's P2P (browser-only by default) runs in Node — the
|
|
21
|
+
// worker then pulls raw shards directly from the uploader's browser over the LAN.
|
|
22
|
+
// `isP2pCapable()` only checks for RTCPeerConnection; set the whole surface.
|
|
23
|
+
for (const k of ["RTCPeerConnection", "RTCDataChannel", "RTCSessionDescription", "RTCIceCandidate"]) {
|
|
24
|
+
if (!globalThis[k] && ndc[k]) globalThis[k] = ndc[k];
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
/** snarkjs (Node) reads circuit assets off disk — download them to a temp dir. */
|
|
20
28
|
async function downloadCircuits(baseUrl) {
|
|
21
29
|
const dir = await mkdtemp(join(tmpdir(), "muhkoo-circuits-"));
|
|
@@ -47,7 +55,10 @@ export async function connect({ baseUrl, appKey, username, password }) {
|
|
|
47
55
|
apiKey: appKey,
|
|
48
56
|
circuits,
|
|
49
57
|
offline: { enabled: false },
|
|
50
|
-
|
|
58
|
+
// P2P on: fetch raw shards from a browser peer over WebRTC. Empty iceServers
|
|
59
|
+
// → direct LAN (host) candidates, no STUN — ideal same-network transcoding;
|
|
60
|
+
// origin remains the fallback. The block engine runs main-thread in Node.
|
|
61
|
+
p2p: { enabled: true, iceServers: [], debug: process.env.MUHKOO_P2P_DEBUG === "1" },
|
|
51
62
|
});
|
|
52
63
|
const user = await client.auth.zk.login(username, password);
|
|
53
64
|
return { client, user };
|
package/src/worker.js
CHANGED
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
* set) are claimable here — a browser's freshly-created job is stored in the
|
|
6
6
|
* background, and the browser can also process it locally from memory.
|
|
7
7
|
*/
|
|
8
|
-
import { hostname } from "node:os";
|
|
8
|
+
import { hostname, cpus } from "node:os";
|
|
9
9
|
import { transcodeToHls } from "./transcode.js";
|
|
10
10
|
|
|
11
11
|
const WORKER_ID = `node-${Math.random().toString(36).slice(2, 10)}`;
|
|
12
12
|
const WORKER_LABEL = `the transcoder (${hostname()})`;
|
|
13
|
+
// Capability score: native ffmpeg on many cores far outranks a browser's wasm
|
|
14
|
+
// (cores×~30), so browsers defer manifest-ready jobs to this machine.
|
|
15
|
+
const MY_SCORE = cpus().length * 30;
|
|
13
16
|
const STALE_MS = 90_000;
|
|
14
17
|
const POLL_MS = 4_000;
|
|
15
18
|
|
|
@@ -69,7 +72,10 @@ export function startWorker({ client, space, appKey, baseUrl }) {
|
|
|
69
72
|
};
|
|
70
73
|
try {
|
|
71
74
|
log(`claim job ${job._id} "${job.title}" — downloading raw…`);
|
|
72
|
-
|
|
75
|
+
// The DB json column can hand back the manifest as a STRING — parse it, or
|
|
76
|
+
// readByManifest chokes ("manifest.chunks is not iterable").
|
|
77
|
+
const manifest = typeof job.input_manifest === "string" ? JSON.parse(job.input_manifest) : job.input_manifest;
|
|
78
|
+
const { data } = await client.storage.readByManifest(manifest);
|
|
73
79
|
log(`transcoding ${(data.length / 1e6).toFixed(1)}MB…`);
|
|
74
80
|
const { hls_index, duration, size } = await transcodeToHls({
|
|
75
81
|
rawBytes: data, filename: job.filename || "input.mp4", space, onProgress, signal: abort.signal,
|
|
@@ -101,7 +107,10 @@ export function startWorker({ client, space, appKey, baseUrl }) {
|
|
|
101
107
|
|
|
102
108
|
let stopped = false;
|
|
103
109
|
(async () => {
|
|
104
|
-
log(`worker ${WORKER_ID} draining queue as ${client.auth.zk.user.username} (${WORKER_LABEL})`);
|
|
110
|
+
log(`worker ${WORKER_ID} draining queue as ${client.auth.zk.user.username} (${WORKER_LABEL}, score ${MY_SCORE})`);
|
|
111
|
+
// Announce capability so browsers defer manifest-ready jobs to us.
|
|
112
|
+
const hello = () => void postSignal({ kind: "hello", score: MY_SCORE, worker: WORKER_LABEL });
|
|
113
|
+
hello(); setInterval(hello, 12_000);
|
|
105
114
|
// Real-time wake on new jobs; the poll below is the durable fallback.
|
|
106
115
|
space.onMessage((e) => { const b = e?.message?.body; if (b?._t === "jobsig" && b.kind === "new" && b.src !== WORKER_ID) tick(); });
|
|
107
116
|
let ticking = false;
|