@meframe/core 0.0.2 → 0.0.3
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/dist/Meframe.d.ts.map +1 -1
- package/dist/Meframe.js +2 -1
- package/dist/Meframe.js.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +2 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/types.d.ts +3 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +2 -1
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/types.d.ts +1 -0
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/stages/compose/types.d.ts +2 -1
- package/dist/stages/compose/types.d.ts.map +1 -1
- package/dist/stages/demux/MP4Demuxer.d.ts +0 -1
- package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
- package/dist/utils/time-utils.d.ts +3 -2
- package/dist/utils/time-utils.d.ts.map +1 -1
- package/dist/utils/time-utils.js +2 -1
- package/dist/utils/time-utils.js.map +1 -1
- package/dist/vite-plugin.d.ts +5 -3
- package/dist/vite-plugin.d.ts.map +1 -1
- package/dist/vite-plugin.js +109 -52
- package/dist/vite-plugin.js.map +1 -1
- package/dist/worker/WorkerPool.d.ts +7 -0
- package/dist/worker/WorkerPool.d.ts.map +1 -1
- package/dist/worker/WorkerPool.js +29 -5
- package/dist/worker/WorkerPool.js.map +1 -1
- package/dist/{stages/demux → workers}/MP4Demuxer.js +4 -13
- package/dist/workers/MP4Demuxer.js.map +1 -0
- package/dist/workers/WorkerChannel.js +486 -0
- package/dist/workers/WorkerChannel.js.map +1 -0
- package/dist/{assets/video-demux.worker-D019I7GQ.js → workers/mp4box.all.js} +4 -912
- package/dist/workers/mp4box.all.js.map +1 -0
- package/dist/{assets/audio-compose.worker-nGVvHD5Q.js → workers/stages/compose/audio-compose.worker.js} +7 -481
- package/dist/workers/stages/compose/audio-compose.worker.js.map +1 -0
- package/dist/{assets/video-compose.worker-DPzsC21d.js → workers/stages/compose/video-compose.worker.js} +7 -481
- package/dist/workers/stages/compose/video-compose.worker.js.map +1 -0
- package/dist/{assets/decode.worker-DpWHsc7R.js → workers/stages/decode/decode.worker.js} +7 -481
- package/dist/workers/stages/decode/decode.worker.js.map +1 -0
- package/dist/{stages → workers/stages}/demux/audio-demux.worker.js +184 -4
- package/dist/workers/stages/demux/audio-demux.worker.js.map +1 -0
- package/dist/{stages → workers/stages}/demux/video-demux.worker.js +2 -3
- package/dist/workers/stages/demux/video-demux.worker.js.map +1 -0
- package/dist/{stages → workers/stages}/encode/encode.worker.js +238 -4
- package/dist/workers/stages/encode/encode.worker.js.map +1 -0
- package/dist/{stages/mux/MP4Muxer.js → workers/stages/mux/mux.worker.js} +244 -5
- package/dist/workers/stages/mux/mux.worker.js.map +1 -0
- package/package.json +21 -21
- package/dist/assets/audio-compose.worker-nGVvHD5Q.js.map +0 -1
- package/dist/assets/audio-demux.worker-xwWBtbAe.js +0 -8299
- package/dist/assets/audio-demux.worker-xwWBtbAe.js.map +0 -1
- package/dist/assets/decode.worker-DpWHsc7R.js.map +0 -1
- package/dist/assets/encode.worker-nfOb3kw6.js +0 -1026
- package/dist/assets/encode.worker-nfOb3kw6.js.map +0 -1
- package/dist/assets/mux.worker-uEMQY066.js +0 -8019
- package/dist/assets/mux.worker-uEMQY066.js.map +0 -1
- package/dist/assets/video-compose.worker-DPzsC21d.js.map +0 -1
- package/dist/assets/video-demux.worker-D019I7GQ.js.map +0 -1
- package/dist/model/types.js +0 -5
- package/dist/model/types.js.map +0 -1
- package/dist/plugins/BackpressureMonitor.js +0 -62
- package/dist/plugins/BackpressureMonitor.js.map +0 -1
- package/dist/stages/compose/AudioDucker.js +0 -161
- package/dist/stages/compose/AudioDucker.js.map +0 -1
- package/dist/stages/compose/AudioMixer.js +0 -373
- package/dist/stages/compose/AudioMixer.js.map +0 -1
- package/dist/stages/compose/FilterProcessor.js +0 -226
- package/dist/stages/compose/FilterProcessor.js.map +0 -1
- package/dist/stages/compose/LayerRenderer.js +0 -215
- package/dist/stages/compose/LayerRenderer.js.map +0 -1
- package/dist/stages/compose/TransitionProcessor.js +0 -189
- package/dist/stages/compose/TransitionProcessor.js.map +0 -1
- package/dist/stages/compose/VideoComposer.js +0 -186
- package/dist/stages/compose/VideoComposer.js.map +0 -1
- package/dist/stages/compose/audio-compose.worker.d.ts +0 -79
- package/dist/stages/compose/audio-compose.worker.d.ts.map +0 -1
- package/dist/stages/compose/audio-compose.worker.js +0 -540
- package/dist/stages/compose/audio-compose.worker.js.map +0 -1
- package/dist/stages/compose/audio-compose.worker2.js +0 -5
- package/dist/stages/compose/audio-compose.worker2.js.map +0 -1
- package/dist/stages/compose/video-compose.worker.d.ts +0 -60
- package/dist/stages/compose/video-compose.worker.d.ts.map +0 -1
- package/dist/stages/compose/video-compose.worker.js +0 -379
- package/dist/stages/compose/video-compose.worker.js.map +0 -1
- package/dist/stages/compose/video-compose.worker2.js +0 -5
- package/dist/stages/compose/video-compose.worker2.js.map +0 -1
- package/dist/stages/decode/AudioChunkDecoder.js +0 -82
- package/dist/stages/decode/AudioChunkDecoder.js.map +0 -1
- package/dist/stages/decode/BaseDecoder.js +0 -130
- package/dist/stages/decode/BaseDecoder.js.map +0 -1
- package/dist/stages/decode/VideoChunkDecoder.js +0 -199
- package/dist/stages/decode/VideoChunkDecoder.js.map +0 -1
- package/dist/stages/decode/decode.worker.d.ts +0 -70
- package/dist/stages/decode/decode.worker.d.ts.map +0 -1
- package/dist/stages/decode/decode.worker.js +0 -423
- package/dist/stages/decode/decode.worker.js.map +0 -1
- package/dist/stages/decode/decode.worker2.js +0 -5
- package/dist/stages/decode/decode.worker2.js.map +0 -1
- package/dist/stages/demux/MP3FrameParser.js +0 -186
- package/dist/stages/demux/MP3FrameParser.js.map +0 -1
- package/dist/stages/demux/MP4Demuxer.js.map +0 -1
- package/dist/stages/demux/audio-demux.worker.d.ts +0 -51
- package/dist/stages/demux/audio-demux.worker.d.ts.map +0 -1
- package/dist/stages/demux/audio-demux.worker.js.map +0 -1
- package/dist/stages/demux/audio-demux.worker2.js +0 -5
- package/dist/stages/demux/audio-demux.worker2.js.map +0 -1
- package/dist/stages/demux/video-demux.worker.d.ts +0 -51
- package/dist/stages/demux/video-demux.worker.d.ts.map +0 -1
- package/dist/stages/demux/video-demux.worker.js.map +0 -1
- package/dist/stages/demux/video-demux.worker2.js +0 -5
- package/dist/stages/demux/video-demux.worker2.js.map +0 -1
- package/dist/stages/encode/AudioChunkEncoder.js +0 -37
- package/dist/stages/encode/AudioChunkEncoder.js.map +0 -1
- package/dist/stages/encode/BaseEncoder.js +0 -164
- package/dist/stages/encode/BaseEncoder.js.map +0 -1
- package/dist/stages/encode/VideoChunkEncoder.js +0 -50
- package/dist/stages/encode/VideoChunkEncoder.js.map +0 -1
- package/dist/stages/encode/encode.worker.d.ts +0 -3
- package/dist/stages/encode/encode.worker.d.ts.map +0 -1
- package/dist/stages/encode/encode.worker.js.map +0 -1
- package/dist/stages/encode/encode.worker2.js +0 -5
- package/dist/stages/encode/encode.worker2.js.map +0 -1
- package/dist/stages/mux/MP4Muxer.js.map +0 -1
- package/dist/stages/mux/mux.worker.d.ts +0 -65
- package/dist/stages/mux/mux.worker.d.ts.map +0 -1
- package/dist/stages/mux/mux.worker.js +0 -219
- package/dist/stages/mux/mux.worker.js.map +0 -1
- package/dist/stages/mux/mux.worker2.js +0 -5
- package/dist/stages/mux/mux.worker2.js.map +0 -1
- package/dist/stages/mux/utils.js +0 -34
- package/dist/stages/mux/utils.js.map +0 -1
- package/dist/worker/worker-registry.d.ts +0 -12
- package/dist/worker/worker-registry.d.ts.map +0 -1
- package/dist/worker/worker-registry.js +0 -20
- package/dist/worker/worker-registry.js.map +0 -1
package/dist/vite-plugin.js
CHANGED
|
@@ -1,72 +1,124 @@
|
|
|
1
|
-
import { existsSync, readdirSync, mkdirSync, copyFileSync } from "fs";
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, copyFileSync } from "fs";
|
|
2
|
+
import { join, dirname, resolve } from "path";
|
|
3
|
+
const DEFAULT_WORKER_PATH = "meframe-workers";
|
|
4
|
+
function findCoreWorkerFiles() {
|
|
5
|
+
const findWorkers = (dir) => {
|
|
6
|
+
if (!existsSync(dir)) return [];
|
|
7
|
+
const files = [];
|
|
8
|
+
const entries = readdirSync(dir);
|
|
9
|
+
for (const entry of entries) {
|
|
10
|
+
const fullPath = join(dir, entry);
|
|
11
|
+
const stat = statSync(fullPath);
|
|
12
|
+
if (stat.isDirectory()) {
|
|
13
|
+
files.push(...findWorkers(fullPath));
|
|
14
|
+
} else if (entry.endsWith(".worker.js") || entry.endsWith(".worker.js.map")) {
|
|
15
|
+
files.push(fullPath);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return files;
|
|
19
|
+
};
|
|
20
|
+
try {
|
|
21
|
+
const corePackagePath = require.resolve("@meframe/core/package.json");
|
|
22
|
+
const coreDir = dirname(corePackagePath);
|
|
23
|
+
const workersDir = join(coreDir, "dist", "workers");
|
|
24
|
+
if (!existsSync(workersDir)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const files = findWorkers(workersDir);
|
|
28
|
+
return files.length > 0 ? { dir: workersDir, files } : null;
|
|
29
|
+
} catch {
|
|
30
|
+
const monorepoPath = resolve(process.cwd(), "../core/dist/workers");
|
|
31
|
+
if (!existsSync(monorepoPath)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const files = findWorkers(monorepoPath);
|
|
35
|
+
return files.length > 0 ? { dir: monorepoPath, files } : null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
3
38
|
function meframePlugin(options = {}) {
|
|
4
|
-
const {
|
|
39
|
+
const { workerPath = DEFAULT_WORKER_PATH, verbose = false } = options;
|
|
5
40
|
let config;
|
|
41
|
+
let coreWorkers = null;
|
|
6
42
|
return {
|
|
7
43
|
name: "vite-plugin-meframe",
|
|
8
44
|
configResolved(resolvedConfig) {
|
|
9
45
|
config = resolvedConfig;
|
|
46
|
+
coreWorkers = findCoreWorkerFiles();
|
|
47
|
+
if (!coreWorkers && config.command === "serve") {
|
|
48
|
+
console.warn("[meframe] Cannot find @meframe/core worker files");
|
|
49
|
+
console.warn("[meframe] Make sure @meframe/core is installed and built");
|
|
50
|
+
}
|
|
10
51
|
},
|
|
11
|
-
|
|
12
|
-
|
|
52
|
+
configureServer(server) {
|
|
53
|
+
const workers = findCoreWorkerFiles();
|
|
54
|
+
if (!workers) {
|
|
55
|
+
console.warn("[meframe] Worker files not found for development");
|
|
13
56
|
return;
|
|
14
57
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
coreAssetsDir = join(coreDir, "dist", "assets");
|
|
21
|
-
} catch {
|
|
22
|
-
const monorepoPath = resolve(process.cwd(), "../core/dist/assets");
|
|
23
|
-
if (existsSync(monorepoPath)) {
|
|
24
|
-
coreAssetsDir = monorepoPath;
|
|
25
|
-
} else {
|
|
26
|
-
console.warn("[meframe] Cannot find @meframe/core worker assets");
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
if (!existsSync(coreAssetsDir)) {
|
|
31
|
-
console.warn("[meframe] Worker assets directory not found:", coreAssetsDir);
|
|
32
|
-
console.warn(
|
|
33
|
-
"[meframe] Please ensure @meframe/core is built: cd node_modules/@meframe/core && pnpm build"
|
|
34
|
-
);
|
|
58
|
+
const workersSourceDir = workers.dir;
|
|
59
|
+
server.middlewares.use((req, res, next) => {
|
|
60
|
+
const url = req.url;
|
|
61
|
+
if (!url || !url.startsWith(`/${workerPath}/`)) {
|
|
62
|
+
next();
|
|
35
63
|
return;
|
|
36
64
|
}
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (workerFiles.length === 0) {
|
|
41
|
-
console.warn("[meframe] No worker files found in:", coreAssetsDir);
|
|
65
|
+
const relativePath = url.substring(`/${workerPath}/`.length).split("?")[0];
|
|
66
|
+
if (!relativePath) {
|
|
67
|
+
next();
|
|
42
68
|
return;
|
|
43
69
|
}
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
mkdirSync(outputDir, { recursive: true });
|
|
47
|
-
let copiedCount = 0;
|
|
48
|
-
const copiedFiles = [];
|
|
49
|
-
for (const file of workerFiles) {
|
|
50
|
-
const src = join(coreAssetsDir, file);
|
|
51
|
-
const dest = join(outputDir, file);
|
|
70
|
+
const filePath = join(workersSourceDir, relativePath);
|
|
71
|
+
if (existsSync(filePath)) {
|
|
52
72
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
const content = readFileSync(filePath);
|
|
74
|
+
const isSourceMap = relativePath.endsWith(".map");
|
|
75
|
+
res.setHeader(
|
|
76
|
+
"Content-Type",
|
|
77
|
+
isSourceMap ? "application/json" : "application/javascript"
|
|
78
|
+
);
|
|
79
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
80
|
+
res.end(content);
|
|
81
|
+
if (verbose) {
|
|
82
|
+
console.log(`[meframe] Served: ${relativePath}`);
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
56
85
|
} catch (error) {
|
|
57
|
-
console.error(`[meframe]
|
|
86
|
+
console.error(`[meframe] Error serving ${relativePath}:`, error);
|
|
87
|
+
res.statusCode = 500;
|
|
88
|
+
res.end("Internal Server Error");
|
|
89
|
+
return;
|
|
58
90
|
}
|
|
59
91
|
}
|
|
92
|
+
next();
|
|
93
|
+
});
|
|
94
|
+
console.log(`[meframe] Development: serving worker files from ${workersSourceDir}`);
|
|
95
|
+
},
|
|
96
|
+
closeBundle() {
|
|
97
|
+
if (config.command !== "build" || !coreWorkers) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const copyDir = (src, dest) => {
|
|
102
|
+
if (!existsSync(src)) return;
|
|
103
|
+
mkdirSync(dest, { recursive: true });
|
|
104
|
+
const entries = readdirSync(src);
|
|
105
|
+
for (const entry of entries) {
|
|
106
|
+
const srcPath = join(src, entry);
|
|
107
|
+
const destPath = join(dest, entry);
|
|
108
|
+
const stat = statSync(srcPath);
|
|
109
|
+
if (stat.isDirectory()) {
|
|
110
|
+
copyDir(srcPath, destPath);
|
|
111
|
+
} else {
|
|
112
|
+
copyFileSync(srcPath, destPath);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const workersSourceDir = coreWorkers.dir;
|
|
117
|
+
const outDir = config.build.outDir || "dist";
|
|
118
|
+
const outputDir = join(process.cwd(), outDir, workerPath);
|
|
119
|
+
copyDir(workersSourceDir, outputDir);
|
|
60
120
|
console.log(`
|
|
61
|
-
[meframe] ✓ Copied
|
|
62
|
-
if (verbose && copiedFiles.length > 0) {
|
|
63
|
-
console.log("[meframe] Files copied:");
|
|
64
|
-
copiedFiles.forEach((file) => console.log(` - ${file}`));
|
|
65
|
-
}
|
|
66
|
-
if (workerDir !== "assets") {
|
|
67
|
-
console.log(`[meframe] ⚠️ Worker files are in ${outDir}/${workerDir}/`);
|
|
68
|
-
console.log(`[meframe] Make sure your server can serve these files.`);
|
|
69
|
-
}
|
|
121
|
+
[meframe] ✓ Copied worker files to ${outDir}/${workerPath}/`);
|
|
70
122
|
} catch (error) {
|
|
71
123
|
console.error("[meframe] Error in meframePlugin:", error);
|
|
72
124
|
}
|
|
@@ -74,8 +126,13 @@ function meframePlugin(options = {}) {
|
|
|
74
126
|
config() {
|
|
75
127
|
return {
|
|
76
128
|
worker: {
|
|
77
|
-
// Ensure worker format is ESM
|
|
78
129
|
format: "es"
|
|
130
|
+
},
|
|
131
|
+
server: {
|
|
132
|
+
fs: {
|
|
133
|
+
// Allow access to @meframe/core in node_modules
|
|
134
|
+
allow: [".."]
|
|
135
|
+
}
|
|
79
136
|
}
|
|
80
137
|
};
|
|
81
138
|
}
|
package/dist/vite-plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin.js","sources":["../src/vite-plugin.ts"],"sourcesContent":["/**\n * Vite Plugin for @meframe/core\n *\n *
|
|
1
|
+
{"version":3,"file":"vite-plugin.js","sources":["../src/vite-plugin.ts"],"sourcesContent":["/**\n * Vite Plugin for @meframe/core\n *\n * Provides unified worker file access in both development and production:\n * - Development: Serves workers via middleware from node_modules\n * - Production: Copies workers to output directory\n *\n * This ensures workers are always accessible at a consistent path.\n *\n * Usage:\n * ```typescript\n * // vite.config.ts\n * import { meframePlugin } from '@meframe/core/vite-plugin';\n *\n * export default defineConfig({\n * plugins: [meframePlugin()]\n * });\n * ```\n */\n\nimport type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport { copyFileSync, mkdirSync, existsSync, readdirSync, statSync, readFileSync } from 'fs';\nimport { join, dirname, resolve } from 'path';\n\n/**\n * Default worker path (matches config/defaults.ts)\n */\nconst DEFAULT_WORKER_PATH = 'meframe-workers';\n\nexport interface MeframePluginOptions {\n /**\n * Virtual path prefix for worker files\n * Workers will be accessible at /{workerPath}/xxx.worker.js\n * Must match the workerPath in Meframe global config\n * @default 'meframe-workers'\n */\n workerPath?: string;\n\n /**\n * Enable verbose logging\n * @default false\n */\n verbose?: boolean;\n}\n\n/**\n * Find @meframe/core worker files\n * Workers are in dist/workers/ (built separately)\n */\nfunction findCoreWorkerFiles(): { dir: string; files: string[] } | null {\n const findWorkers = (dir: string): string[] => {\n if (!existsSync(dir)) return [];\n\n const files: string[] = [];\n const entries = readdirSync(dir);\n\n for (const entry of entries) {\n const fullPath = join(dir, entry);\n const stat = statSync(fullPath);\n\n if (stat.isDirectory()) {\n files.push(...findWorkers(fullPath));\n } else if (entry.endsWith('.worker.js') || entry.endsWith('.worker.js.map')) {\n files.push(fullPath);\n }\n }\n\n return files;\n };\n\n try {\n // Try to resolve from node_modules\n const corePackagePath = require.resolve('@meframe/core/package.json');\n const coreDir = dirname(corePackagePath);\n const workersDir = join(coreDir, 'dist', 'workers');\n\n if (!existsSync(workersDir)) {\n return null;\n }\n\n const files = findWorkers(workersDir);\n return files.length > 0 ? { dir: workersDir, files } : null;\n } catch {\n // Fallback for monorepo development\n const monorepoPath = resolve(process.cwd(), '../core/dist/workers');\n if (!existsSync(monorepoPath)) {\n return null;\n }\n\n const files = findWorkers(monorepoPath);\n return files.length > 0 ? { dir: monorepoPath, files } : null;\n }\n}\n\nexport function meframePlugin(options: MeframePluginOptions = {}): Plugin {\n const { workerPath = DEFAULT_WORKER_PATH, verbose = false } = options;\n let config: ResolvedConfig;\n let coreWorkers: { dir: string; files: string[] } | null = null;\n\n return {\n name: 'vite-plugin-meframe',\n\n configResolved(resolvedConfig) {\n config = resolvedConfig;\n coreWorkers = findCoreWorkerFiles();\n\n if (!coreWorkers && config.command === 'serve') {\n console.warn('[meframe] Cannot find @meframe/core worker files');\n console.warn('[meframe] Make sure @meframe/core is installed and built');\n }\n },\n\n configureServer(server: ViteDevServer) {\n // Development mode: Serve worker files via middleware\n const workers = findCoreWorkerFiles();\n\n if (!workers) {\n console.warn('[meframe] Worker files not found for development');\n return;\n }\n\n const workersSourceDir = workers.dir; // dist/workers\n\n // Middleware to serve files from dist/workers\n server.middlewares.use((req, res, next) => {\n const url = req.url;\n if (!url || !url.startsWith(`/${workerPath}/`)) {\n next();\n return;\n }\n\n // Remove the workerPath prefix to get relative path\n // e.g., /meframe-workers/stages/decode/decode.worker.js -> stages/decode/decode.worker.js\n const relativePath = url.substring(`/${workerPath}/`.length).split('?')[0];\n\n if (!relativePath) {\n next();\n return;\n }\n\n const filePath = join(workersSourceDir, relativePath);\n\n // Check if file exists\n if (existsSync(filePath)) {\n try {\n const content = readFileSync(filePath);\n const isSourceMap = relativePath.endsWith('.map');\n\n res.setHeader(\n 'Content-Type',\n isSourceMap ? 'application/json' : 'application/javascript'\n );\n res.setHeader('Cache-Control', 'no-cache');\n res.end(content);\n\n if (verbose) {\n console.log(`[meframe] Served: ${relativePath}`);\n }\n return;\n } catch (error) {\n console.error(`[meframe] Error serving ${relativePath}:`, error);\n res.statusCode = 500;\n res.end('Internal Server Error');\n return;\n }\n }\n\n next();\n });\n\n console.log(`[meframe] Development: serving worker files from ${workersSourceDir}`);\n },\n\n closeBundle() {\n // Only run in build mode\n if (config.command !== 'build' || !coreWorkers) {\n return;\n }\n\n try {\n const copyDir = (src: string, dest: string) => {\n if (!existsSync(src)) return;\n\n mkdirSync(dest, { recursive: true });\n const entries = readdirSync(src);\n\n for (const entry of entries) {\n const srcPath = join(src, entry);\n const destPath = join(dest, entry);\n const stat = statSync(srcPath);\n\n if (stat.isDirectory()) {\n copyDir(srcPath, destPath);\n } else {\n copyFileSync(srcPath, destPath);\n }\n }\n };\n\n const workersSourceDir = coreWorkers.dir; // dist/workers\n const outDir = config.build.outDir || 'dist';\n const outputDir = join(process.cwd(), outDir, workerPath);\n\n // Copy entire dist/workers structure\n copyDir(workersSourceDir, outputDir);\n\n // Log results\n console.log(`\\n[meframe] ✓ Copied worker files to ${outDir}/${workerPath}/`);\n } catch (error) {\n console.error('[meframe] Error in meframePlugin:', error);\n }\n },\n\n config() {\n return {\n worker: {\n format: 'es',\n },\n server: {\n fs: {\n // Allow access to @meframe/core in node_modules\n allow: ['..'],\n },\n },\n };\n },\n };\n}\n\nexport default meframePlugin;\n"],"names":[],"mappings":"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"}
|
|
@@ -6,12 +6,19 @@ import { EventPayloadMap } from '../event/events';
|
|
|
6
6
|
export interface WorkerPoolConfig {
|
|
7
7
|
eventBus: EventBus<EventPayloadMap>;
|
|
8
8
|
workerConfigs?: Record<WorkerType, any>;
|
|
9
|
+
/** Worker files base path (default: '/meframe-workers') */
|
|
10
|
+
workerPath?: string;
|
|
9
11
|
}
|
|
10
12
|
export declare class WorkerPool {
|
|
11
13
|
private pool;
|
|
12
14
|
private eventBus;
|
|
13
15
|
private workerConfigs;
|
|
16
|
+
private workerPath;
|
|
14
17
|
constructor(config: WorkerPoolConfig);
|
|
18
|
+
/**
|
|
19
|
+
* Get worker URL for a specific worker type
|
|
20
|
+
*/
|
|
21
|
+
private getWorkerUrl;
|
|
15
22
|
/**
|
|
16
23
|
* Get or create a worker instance
|
|
17
24
|
* @param type - Worker type
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkerPool.d.ts","sourceRoot":"","sources":["../../src/worker/WorkerPool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAA0B,MAAM,SAAS,CAAC;AAEhF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"WorkerPool.d.ts","sourceRoot":"","sources":["../../src/worker/WorkerPool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAA0B,MAAM,SAAS,CAAC;AAEhF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACxC,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAeD,qBAAa,UAAU;IACrB,OAAO,CAAC,IAAI,CAAiC;IAC7C,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,UAAU,CAAS;gBAEf,MAAM,EAAE,gBAAgB;IAMpC;;OAEG;IACH,OAAO,CAAC,YAAY;IAmBpB;;;;;;OAMG;IACG,GAAG,CACP,IAAI,EAAE,UAAU,EAChB,EAAE,CAAC,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,cAAc,CAAC,EAAE,OAAO,CAAA;KAAE,GACrD,OAAO,CAAC,UAAU,CAAC;IAsChB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB7D;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAU9C;;OAEG;IACH,YAAY,IAAI,IAAI;IAOpB;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,UAAU,CAAC,CAAC,CAQrD;IAED;;OAEG;IACH,IAAI,aAAa,IAAI,MAAM,EAAE,CAE5B;IAED;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO;CAI5C"}
|
|
@@ -1,13 +1,40 @@
|
|
|
1
1
|
import { BaseWorker } from "./BaseWorker.js";
|
|
2
2
|
import { WorkerMessageType } from "./types.js";
|
|
3
|
-
|
|
3
|
+
const WORKER_FILE_NAMES = {
|
|
4
|
+
videoDemux: "video-demux",
|
|
5
|
+
audioDemux: "audio-demux",
|
|
6
|
+
decode: "decode",
|
|
7
|
+
videoCompose: "video-compose",
|
|
8
|
+
audioCompose: "audio-compose",
|
|
9
|
+
encode: "encode",
|
|
10
|
+
mux: "mux"
|
|
11
|
+
};
|
|
4
12
|
class WorkerPool {
|
|
5
13
|
pool = /* @__PURE__ */ new Map();
|
|
6
14
|
eventBus;
|
|
7
15
|
workerConfigs;
|
|
16
|
+
workerPath;
|
|
8
17
|
constructor(config) {
|
|
9
18
|
this.eventBus = config.eventBus;
|
|
10
19
|
this.workerConfigs = config.workerConfigs || {};
|
|
20
|
+
this.workerPath = config.workerPath || "/meframe-workers";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get worker URL for a specific worker type
|
|
24
|
+
*/
|
|
25
|
+
getWorkerUrl(type) {
|
|
26
|
+
const fileName = WORKER_FILE_NAMES[type];
|
|
27
|
+
const stageMap = {
|
|
28
|
+
"video-demux": "demux",
|
|
29
|
+
"audio-demux": "demux",
|
|
30
|
+
decode: "decode",
|
|
31
|
+
"video-compose": "compose",
|
|
32
|
+
"audio-compose": "compose",
|
|
33
|
+
encode: "encode",
|
|
34
|
+
mux: "mux"
|
|
35
|
+
};
|
|
36
|
+
const stage = stageMap[fileName];
|
|
37
|
+
return `${this.workerPath}/stages/${stage}/${fileName}.worker.js`;
|
|
11
38
|
}
|
|
12
39
|
/**
|
|
13
40
|
* Get or create a worker instance
|
|
@@ -20,10 +47,7 @@ class WorkerPool {
|
|
|
20
47
|
const key = id ? `${type}#${id}` : type;
|
|
21
48
|
const existing = this.pool.get(key);
|
|
22
49
|
if (!existing) {
|
|
23
|
-
const url =
|
|
24
|
-
if (!url) {
|
|
25
|
-
throw new Error(`Worker URL not found for type: ${type}`);
|
|
26
|
-
}
|
|
50
|
+
const url = this.getWorkerUrl(type);
|
|
27
51
|
const worker = new BaseWorker({
|
|
28
52
|
type,
|
|
29
53
|
url,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkerPool.js","sources":["../../src/worker/WorkerPool.ts"],"sourcesContent":["/**\n * WorkerPool: Manages worker instances with key-based access\n * Provides lazy creation and unified management of workers\n */\n\nimport { BaseWorker } from './BaseWorker';\nimport type { WorkerType, WorkerStatus, WorkerConfigurePayload } from './types';\nimport { WorkerMessageType } from './types';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\
|
|
1
|
+
{"version":3,"file":"WorkerPool.js","sources":["../../src/worker/WorkerPool.ts"],"sourcesContent":["/**\n * WorkerPool: Manages worker instances with key-based access\n * Provides lazy creation and unified management of workers\n */\n\nimport { BaseWorker } from './BaseWorker';\nimport type { WorkerType, WorkerStatus, WorkerConfigurePayload } from './types';\nimport { WorkerMessageType } from './types';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\n\nexport interface WorkerPoolConfig {\n eventBus: EventBus<EventPayloadMap>;\n workerConfigs?: Record<WorkerType, any>;\n /** Worker files base path (default: '/meframe-workers') */\n workerPath?: string;\n}\n\n/**\n * Worker name mapping (worker type -> file name)\n */\nconst WORKER_FILE_NAMES: Record<WorkerType, string> = {\n videoDemux: 'video-demux',\n audioDemux: 'audio-demux',\n decode: 'decode',\n videoCompose: 'video-compose',\n audioCompose: 'audio-compose',\n encode: 'encode',\n mux: 'mux',\n};\n\nexport class WorkerPool {\n private pool = new Map<string, BaseWorker>();\n private eventBus: EventBus<EventPayloadMap>;\n private workerConfigs: Record<string, any>;\n private workerPath: string;\n\n constructor(config: WorkerPoolConfig) {\n this.eventBus = config.eventBus;\n this.workerConfigs = config.workerConfigs || {};\n this.workerPath = config.workerPath || '/meframe-workers';\n }\n\n /**\n * Get worker URL for a specific worker type\n */\n private getWorkerUrl(type: WorkerType): string {\n const fileName = WORKER_FILE_NAMES[type];\n\n // Map worker type to its stage directory\n const stageMap: Record<string, string> = {\n 'video-demux': 'demux',\n 'audio-demux': 'demux',\n decode: 'decode',\n 'video-compose': 'compose',\n 'audio-compose': 'compose',\n encode: 'encode',\n mux: 'mux',\n };\n const stage = stageMap[fileName];\n\n // Workers are in stages subdirectory\n return `${this.workerPath}/stages/${stage}/${fileName}.worker.js`;\n }\n\n /**\n * Get or create a worker instance\n * @param type - Worker type\n * @param id - Optional ID for per-resource or per-clip workers\n * @param options - Optional configuration\n * - lazy: If true, skip initial configure (default: false)\n */\n async get(\n type: WorkerType,\n id?: string,\n options?: { lazy?: boolean; skipInitialize?: boolean }\n ): Promise<BaseWorker> {\n const key = id ? `${type}#${id}` : type;\n\n const existing = this.pool.get(key);\n if (!existing) {\n // Generate worker URL based on worker path and type\n const url = this.getWorkerUrl(type);\n\n const worker = new BaseWorker({\n type,\n url,\n eventBus: this.eventBus,\n clipId: id,\n });\n\n // Only initialize if not in lazy mode\n if (!options?.lazy && !options?.skipInitialize) {\n const config = this.workerConfigs[type];\n await worker.initialize(config);\n }\n\n this.pool.set(key, worker);\n return worker;\n }\n\n if (!options?.lazy && !options?.skipInitialize) {\n const config = this.workerConfigs[type];\n if (config) {\n await existing.send(WorkerMessageType.Configure, {\n config,\n initial: false,\n } as WorkerConfigurePayload);\n }\n }\n\n return existing;\n }\n\n async setConfig(type: WorkerType, config: any): Promise<void> {\n const existing = this.workerConfigs[type] || {};\n const mergedConfig = { ...existing, ...config };\n\n this.workerConfigs[type] = mergedConfig;\n\n for (const [key, worker] of this.pool.entries()) {\n if (key === type || key.startsWith(`${type}#`)) {\n await worker.send(WorkerMessageType.Configure, {\n config: mergedConfig,\n initial: false,\n } as WorkerConfigurePayload);\n }\n }\n }\n\n /**\n * Terminate a specific worker\n */\n terminate(type: WorkerType, id?: string): void {\n const key = id ? `${type}#${id}` : type;\n const worker = this.pool.get(key);\n\n if (worker) {\n worker.terminate();\n this.pool.delete(key);\n }\n }\n\n /**\n * Terminate all workers\n */\n terminateAll(): void {\n for (const worker of this.pool.values()) {\n worker.terminate();\n }\n this.pool.clear();\n }\n\n /**\n * Get status of all workers\n */\n get status(): Record<string, WorkerStatus[WorkerType]> {\n const result: Record<string, WorkerStatus[WorkerType]> = {};\n\n for (const [key, worker] of this.pool.entries()) {\n result[key] = worker.status;\n }\n\n return result;\n }\n\n /**\n * Get list of active worker keys\n */\n get activeWorkers(): string[] {\n return Array.from(this.pool.keys());\n }\n\n /**\n * Check if a worker exists\n */\n has(type: WorkerType, id?: string): boolean {\n const key = id ? `${type}#${id}` : type;\n return this.pool.has(key);\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import "
|
|
2
|
-
import { BackpressureMonitor } from "../../plugins/BackpressureMonitor.js";
|
|
3
|
-
import { __exports as mp4box_all } from "../../_virtual/mp4box.all.js";
|
|
1
|
+
import { m as mp4box_all } from "./mp4box.all.js";
|
|
4
2
|
class MP4Demuxer {
|
|
5
3
|
mp4boxFile;
|
|
6
4
|
tracks = /* @__PURE__ */ new Map();
|
|
7
5
|
isReady = false;
|
|
8
6
|
videoController;
|
|
9
7
|
audioController;
|
|
10
|
-
backpressureMonitor;
|
|
11
8
|
demuxHighWaterMark;
|
|
12
9
|
onReadyCallback;
|
|
13
10
|
fileOffset = 0;
|
|
@@ -15,7 +12,6 @@ class MP4Demuxer {
|
|
|
15
12
|
audioTimestampOffset = null;
|
|
16
13
|
constructor(config = {}) {
|
|
17
14
|
this.mp4boxFile = mp4box_all.createFile();
|
|
18
|
-
this.backpressureMonitor = new BackpressureMonitor();
|
|
19
15
|
this.onReadyCallback = config.onReady;
|
|
20
16
|
const DEFAULT_HIGH_WATER_MARK = 10;
|
|
21
17
|
this.demuxHighWaterMark = config.highWaterMark ?? DEFAULT_HIGH_WATER_MARK;
|
|
@@ -155,9 +151,7 @@ class MP4Demuxer {
|
|
|
155
151
|
start: (controller) => {
|
|
156
152
|
this.videoController = controller;
|
|
157
153
|
},
|
|
158
|
-
transform: (chunk,
|
|
159
|
-
const desiredSize = controller.desiredSize ?? this.demuxHighWaterMark;
|
|
160
|
-
this.backpressureMonitor.updateMetrics("demux-video", desiredSize);
|
|
154
|
+
transform: (chunk, _controller) => {
|
|
161
155
|
const chunkData = new Uint8Array(chunk);
|
|
162
156
|
this.appendBuffer(chunkData);
|
|
163
157
|
this.mp4boxFile.flush();
|
|
@@ -186,9 +180,7 @@ class MP4Demuxer {
|
|
|
186
180
|
start: (controller) => {
|
|
187
181
|
this.audioController = controller;
|
|
188
182
|
},
|
|
189
|
-
transform: (chunk,
|
|
190
|
-
const desiredSize = controller.desiredSize ?? this.demuxHighWaterMark;
|
|
191
|
-
this.backpressureMonitor.updateMetrics("demux-audio", desiredSize);
|
|
183
|
+
transform: (chunk, _controller) => {
|
|
192
184
|
const chunkData = new Uint8Array(chunk);
|
|
193
185
|
this.appendBuffer(chunkData);
|
|
194
186
|
this.mp4boxFile.flush();
|
|
@@ -226,13 +218,12 @@ class MP4Demuxer {
|
|
|
226
218
|
this.mp4boxFile?.stop();
|
|
227
219
|
this.mp4boxFile = null;
|
|
228
220
|
this.tracks.clear();
|
|
229
|
-
this.backpressureMonitor.clear();
|
|
230
221
|
this.isReady = false;
|
|
231
222
|
this.videoTimestampOffset = null;
|
|
232
223
|
this.audioTimestampOffset = null;
|
|
233
224
|
}
|
|
234
225
|
}
|
|
235
226
|
export {
|
|
236
|
-
MP4Demuxer
|
|
227
|
+
MP4Demuxer as M
|
|
237
228
|
};
|
|
238
229
|
//# sourceMappingURL=MP4Demuxer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MP4Demuxer.js","sources":["../../src/stages/demux/MP4Demuxer.ts"],"sourcesContent":["import * as MP4Box from 'mp4box';\nimport type { DemuxConfig, TrackInfo } from './types';\n\n/**\n * MP4 Demuxer - Extract encoded chunks from MP4 container\n * Simplified implementation following Stream API pattern\n */\nexport class MP4Demuxer {\n private mp4boxFile: any;\n tracks = new Map<number, TrackInfo>();\n isReady = false;\n private videoController?: TransformStreamDefaultController<EncodedVideoChunk>;\n private audioController?: TransformStreamDefaultController<EncodedAudioChunk>;\n private demuxHighWaterMark: number;\n private onReadyCallback?: () => void;\n private fileOffset = 0;\n private videoTimestampOffset: number | null = null;\n private audioTimestampOffset: number | null = null;\n\n constructor(config: DemuxConfig & { onReady?: () => void } = {}) {\n this.mp4boxFile = MP4Box.createFile();\n this.onReadyCallback = config.onReady;\n\n // Use provided config with local default as fallback\n const DEFAULT_HIGH_WATER_MARK = 10; // Default for video chunks\n this.demuxHighWaterMark = config.highWaterMark ?? DEFAULT_HIGH_WATER_MARK;\n\n this.setupHandlers();\n }\n\n updateConfig(config: DemuxConfig): void {\n this.demuxHighWaterMark = config.highWaterMark ?? this.demuxHighWaterMark;\n }\n\n private setupHandlers(): void {\n this.mp4boxFile.onError = (error: string) => {\n console.error('MP4Box error:', error);\n this.videoController?.error(new Error(error));\n this.audioController?.error(new Error(error));\n };\n\n this.mp4boxFile.onReady = (info: any) => {\n this.processTracks(info.tracks);\n this.isReady = true;\n\n // Call the ready callback before starting\n if (this.onReadyCallback) {\n this.onReadyCallback();\n }\n\n // Start processing samples after callback\n // Note: start() enables extraction, actual samples will be output\n // when flush() is called (by TransformStream's flush callback)\n this.mp4boxFile.start();\n };\n\n this.mp4boxFile.onSamples = (trackId: number, _user: any, samples: any[]) => {\n this.processSamples(trackId, samples);\n };\n }\n\n private processTracks(tracks: any[]): void {\n for (const track of tracks) {\n const trackInfo: TrackInfo = {\n id: track.id,\n type: track.type === 'video' ? 'video' : 'audio',\n codec: track.codec,\n timescale: track.timescale,\n };\n\n if (track.type === 'video') {\n trackInfo.width = track.video?.width;\n trackInfo.height = track.video?.height;\n trackInfo.description = this.getVideoDescription(track);\n } else if (track.type === 'audio') {\n trackInfo.sampleRate = track.audio?.sample_rate;\n trackInfo.numberOfChannels = track.audio?.channel_count;\n trackInfo.description = this.getAudioDescription(track);\n }\n\n this.tracks.set(track.id, trackInfo);\n\n // Configure extraction\n this.mp4boxFile.setExtractionOptions(track.id, track, {\n nbSamples: 30, // Batch size per callback (balance between latency and overhead)\n });\n }\n }\n\n private processSamples(trackId: number, samples: any[]): void {\n const track = this.tracks.get(trackId);\n if (!track) return;\n\n const timescale = track.timescale || 90000;\n for (const sample of samples) {\n const rawTimestamp = (sample.cts * 1000000) / timescale;\n const duration = (sample.duration * 1000000) / timescale;\n\n if (track.type === 'video') {\n if (!this.videoController) {\n console.error('[MP4Demuxer] videoController is null when trying to output chunk!');\n // Should not happen - stream should be created before appendBuffer\n return;\n }\n\n // Normalize timestamp: first frame starts at 0\n if (this.videoTimestampOffset === null) {\n this.videoTimestampOffset = rawTimestamp;\n }\n const timestamp = rawTimestamp - this.videoTimestampOffset;\n\n const chunk = new EncodedVideoChunk({\n type: sample.is_sync ? 'key' : 'delta',\n timestamp,\n duration,\n data: sample.data,\n });\n this.videoController.enqueue(chunk);\n } else if (track.type === 'audio' && this.audioController) {\n // Normalize timestamp: first frame starts at 0\n if (this.audioTimestampOffset === null) {\n this.audioTimestampOffset = rawTimestamp;\n }\n const timestamp = rawTimestamp - this.audioTimestampOffset;\n\n const chunk = new EncodedAudioChunk({\n type: 'key',\n timestamp,\n duration,\n data: sample.data,\n });\n this.audioController.enqueue(chunk);\n }\n }\n\n const last = samples[samples.length - 1].number;\n // Release memory immediately\n this.mp4boxFile.releaseUsedSamples(trackId, last + 1);\n }\n\n private getVideoDescription(track: any): ArrayBuffer | undefined {\n try {\n const fullTrack = this.mp4boxFile.getTrackById(track.id);\n for (const entry of fullTrack.mdia.minf.stbl.stsd.entries) {\n const box = entry.avcC ?? entry.hvcC ?? entry.av1C ?? entry.vpcC;\n if (box) {\n const stream = new (MP4Box as any).DataStream(\n undefined,\n 0,\n (MP4Box as any).DataStream.BIG_ENDIAN // IMPORTANT: must be BIG_ENDIAN\n );\n box.write(stream);\n return new Uint8Array(stream.buffer.slice(8)).buffer;\n }\n }\n } catch (error) {\n console.error('Failed to get video description:', error);\n }\n return undefined;\n }\n\n // private getVideoDescription(track: any): ArrayBuffer | undefined {\n // if (!this.mp4boxFile) return undefined;\n // return getVideoDescription(this.mp4boxFile, track);\n // }\n\n private getAudioDescription(track: any): ArrayBuffer | undefined {\n try {\n const fullTrack = this.mp4boxFile.getTrackById(track.id);\n for (const entry of fullTrack.mdia.minf.stbl.stsd.entries) {\n if (entry.esds || entry.dOps) {\n const stream = new (MP4Box as any).DataStream();\n (entry.esds || entry.dOps).write(stream);\n return new Uint8Array(stream.buffer.slice(8)).buffer;\n }\n }\n } catch (error) {\n console.error('Failed to get audio description:', error);\n }\n return undefined;\n }\n\n /**\n * Create transform stream for video track\n */\n createVideoStream(): TransformStream<Uint8Array, EncodedVideoChunk> {\n // const hasVideo = Array.from(this.tracks.values()).some((t) => t.type === 'video');\n return new TransformStream<Uint8Array, EncodedVideoChunk>(\n {\n start: (controller) => {\n this.videoController = controller;\n },\n transform: (chunk, _controller) => {\n // Create a copy to avoid buffer reuse issues (required for mp4box 0.5.x)\n const chunkData = new Uint8Array(chunk);\n this.appendBuffer(chunkData);\n\n // Flush after each append to trigger sample extraction immediately\n this.mp4boxFile.flush();\n },\n flush: async () => {\n // Trigger MP4Box flush\n this.mp4boxFile.flush();\n\n // Wait for MP4Box to complete sample extraction (onSamples callbacks)\n // Give MP4Box time to process asynchronously\n await new Promise((resolve) => setTimeout(resolve, 100));\n },\n },\n // Queuing strategy: use configuration\n {\n highWaterMark: this.demuxHighWaterMark,\n size: () => 1, // Count-based\n }\n );\n }\n\n /**\n * Create transform stream for audio track\n */\n createAudioStream(): TransformStream<Uint8Array, EncodedAudioChunk> | null {\n const hasAudio = Array.from(this.tracks.values()).some((t) => t.type === 'audio');\n if (!hasAudio) return null;\n\n return new TransformStream<Uint8Array, EncodedAudioChunk>(\n {\n start: (controller) => {\n this.audioController = controller;\n },\n transform: (chunk, _controller) => {\n // Create a copy to avoid buffer reuse issues (required for mp4box 0.5.x)\n const chunkData = new Uint8Array(chunk);\n this.appendBuffer(chunkData);\n\n // Flush after each append to trigger sample extraction immediately\n this.mp4boxFile.flush();\n },\n flush: () => {\n this.mp4boxFile.flush();\n },\n },\n // Queuing strategy: use configuration\n {\n highWaterMark: this.demuxHighWaterMark,\n size: () => 1,\n }\n );\n }\n\n appendBuffer(chunk: Uint8Array): void {\n const buffer = chunk.buffer as ArrayBuffer & { fileStart: number };\n buffer.fileStart = this.fileOffset;\n this.mp4boxFile.appendBuffer(buffer);\n this.fileOffset += chunk.byteLength;\n }\n\n /**\n * Get video track info if available\n */\n get videoTrackInfo(): TrackInfo | undefined {\n return Array.from(this.tracks.values()).find((track) => track.type === 'video');\n }\n\n /**\n * Get audio track info if available\n */\n get audioTrackInfo(): TrackInfo | undefined {\n return Array.from(this.tracks.values()).find((track) => track.type === 'audio');\n }\n\n destroy(): void {\n this.mp4boxFile?.stop();\n this.mp4boxFile = null;\n this.tracks.clear();\n this.isReady = false;\n this.videoTimestampOffset = null;\n this.audioTimestampOffset = null;\n }\n}\n"],"names":["MP4Box.createFile","MP4Box.DataStream"],"mappings":"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"}
|