@meframe/core 0.0.28 → 0.0.30-beta
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 +2 -13
- package/dist/Meframe.d.ts.map +1 -1
- package/dist/Meframe.js +6 -100
- package/dist/Meframe.js.map +1 -1
- package/dist/cache/CacheManager.d.ts +35 -19
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +223 -134
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/l1/VideoL1Cache.d.ts +15 -2
- package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
- package/dist/cache/l1/VideoL1Cache.js +58 -38
- package/dist/cache/l1/VideoL1Cache.js.map +1 -1
- package/dist/cache/l2/L2Cache.d.ts.map +1 -1
- package/dist/cache/l2/L2Cache.js +5 -5
- package/dist/cache/l2/L2Cache.js.map +1 -1
- package/dist/cache/l2/L2OPFSStore.d.ts +37 -0
- package/dist/cache/l2/L2OPFSStore.d.ts.map +1 -0
- package/dist/cache/l2/L2OPFSStore.js +89 -0
- package/dist/cache/l2/L2OPFSStore.js.map +1 -0
- package/dist/cache/resource/AudioSampleCache.d.ts +52 -0
- package/dist/cache/resource/AudioSampleCache.d.ts.map +1 -0
- package/dist/cache/resource/AudioSampleCache.js +69 -0
- package/dist/cache/resource/AudioSampleCache.js.map +1 -0
- package/dist/cache/resource/ImageBitmapCache.d.ts +65 -0
- package/dist/cache/resource/ImageBitmapCache.d.ts.map +1 -0
- package/dist/cache/resource/ImageBitmapCache.js +101 -0
- package/dist/cache/resource/ImageBitmapCache.js.map +1 -0
- package/dist/cache/resource/MP4IndexCache.d.ts +48 -0
- package/dist/cache/resource/MP4IndexCache.d.ts.map +1 -0
- package/dist/cache/resource/MP4IndexCache.js +104 -0
- package/dist/cache/resource/MP4IndexCache.js.map +1 -0
- package/dist/cache/resource/ResourceCache.d.ts +46 -0
- package/dist/cache/resource/ResourceCache.d.ts.map +1 -0
- package/dist/cache/resource/ResourceCache.js +92 -0
- package/dist/cache/resource/ResourceCache.js.map +1 -0
- package/dist/cache/storage/indexeddb/ChunkRecordStore.d.ts +75 -0
- package/dist/cache/storage/indexeddb/ChunkRecordStore.d.ts.map +1 -0
- package/dist/cache/{l2/IndexedDBStore.js → storage/indexeddb/ChunkRecordStore.js} +3 -3
- package/dist/cache/storage/indexeddb/ChunkRecordStore.js.map +1 -0
- package/dist/cache/storage/opfs/OPFSManager.d.ts +54 -0
- package/dist/cache/storage/opfs/OPFSManager.d.ts.map +1 -0
- package/dist/cache/storage/opfs/OPFSManager.js +133 -0
- package/dist/cache/storage/opfs/OPFSManager.js.map +1 -0
- package/dist/cache/storage/opfs/types.d.ts +16 -0
- package/dist/cache/storage/opfs/types.d.ts.map +1 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +21 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/types.d.ts +28 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/controllers/ExportController.d.ts +16 -0
- package/dist/controllers/ExportController.d.ts.map +1 -0
- package/dist/controllers/ExportController.js +44 -0
- package/dist/controllers/ExportController.js.map +1 -0
- package/dist/controllers/PlaybackController.d.ts +28 -4
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +117 -52
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/controllers/index.d.ts +2 -3
- package/dist/controllers/index.d.ts.map +1 -1
- package/dist/controllers/types.d.ts +0 -28
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/event/events.d.ts +8 -0
- package/dist/event/events.d.ts.map +1 -1
- package/dist/event/events.js +1 -0
- package/dist/event/events.js.map +1 -1
- package/dist/model/CompositionModel.d.ts.map +1 -1
- package/dist/model/CompositionModel.js +11 -6
- package/dist/model/CompositionModel.js.map +1 -1
- package/dist/model/RcFrame.d.ts +2 -0
- package/dist/model/RcFrame.d.ts.map +1 -1
- package/dist/model/RcFrame.js +3 -0
- package/dist/model/RcFrame.js.map +1 -1
- package/dist/orchestrator/ExportScheduler.d.ts +35 -0
- package/dist/orchestrator/ExportScheduler.d.ts.map +1 -0
- package/dist/orchestrator/ExportScheduler.js +241 -0
- package/dist/orchestrator/ExportScheduler.js.map +1 -0
- package/dist/orchestrator/GlobalAudioSession.d.ts +21 -7
- package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.js +132 -140
- package/dist/orchestrator/GlobalAudioSession.js.map +1 -1
- package/dist/orchestrator/OnDemandVideoSession.d.ts +73 -0
- package/dist/orchestrator/OnDemandVideoSession.d.ts.map +1 -0
- package/dist/orchestrator/OnDemandVideoSession.js +281 -0
- package/dist/orchestrator/OnDemandVideoSession.js.map +1 -0
- package/dist/orchestrator/Orchestrator.d.ts +22 -17
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +234 -301
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +3 -15
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/orchestrator/index.d.ts +0 -1
- package/dist/orchestrator/index.d.ts.map +1 -1
- package/dist/orchestrator/types.d.ts +4 -4
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/stages/compose/FilterProcessor.d.ts +1 -1
- package/dist/stages/compose/FilterProcessor.d.ts.map +1 -1
- package/dist/stages/compose/FilterProcessor.js +226 -0
- package/dist/stages/compose/FilterProcessor.js.map +1 -0
- package/dist/stages/compose/FrameRateConverter.d.ts +68 -0
- package/dist/stages/compose/FrameRateConverter.d.ts.map +1 -0
- package/dist/stages/compose/LayerRenderer.d.ts +1 -1
- package/dist/stages/compose/LayerRenderer.d.ts.map +1 -1
- package/dist/stages/compose/LayerRenderer.js +270 -0
- package/dist/stages/compose/LayerRenderer.js.map +1 -0
- package/dist/stages/compose/TransitionProcessor.d.ts +1 -1
- package/dist/stages/compose/TransitionProcessor.d.ts.map +1 -1
- package/dist/stages/compose/TransitionProcessor.js +189 -0
- package/dist/stages/compose/TransitionProcessor.js.map +1 -0
- package/dist/stages/compose/VideoComposer.d.ts +6 -4
- package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
- package/dist/stages/compose/VideoComposer.js +229 -0
- package/dist/stages/compose/VideoComposer.js.map +1 -0
- package/dist/stages/compose/text-renderers/animation-utils.js +76 -0
- package/dist/stages/compose/text-renderers/animation-utils.js.map +1 -0
- package/dist/stages/compose/text-renderers/basic-text-renderer.d.ts +2 -2
- package/dist/stages/compose/text-renderers/basic-text-renderer.d.ts.map +1 -1
- package/dist/stages/compose/text-renderers/basic-text-renderer.js +93 -0
- package/dist/stages/compose/text-renderers/basic-text-renderer.js.map +1 -0
- package/dist/stages/compose/text-renderers/character-ktv-renderer.d.ts +1 -1
- package/dist/stages/compose/text-renderers/character-ktv-renderer.d.ts.map +1 -1
- package/dist/stages/compose/text-renderers/character-ktv-renderer.js +132 -0
- package/dist/stages/compose/text-renderers/character-ktv-renderer.js.map +1 -0
- package/dist/stages/compose/text-renderers/word-by-word-renderer.d.ts +1 -1
- package/dist/stages/compose/text-renderers/word-by-word-renderer.d.ts.map +1 -1
- package/dist/stages/compose/text-renderers/word-by-word-renderer.js +128 -0
- package/dist/stages/compose/text-renderers/word-by-word-renderer.js.map +1 -0
- package/dist/stages/compose/text-renderers/word-fancy-renderer.d.ts +1 -1
- package/dist/stages/compose/text-renderers/word-fancy-renderer.d.ts.map +1 -1
- package/dist/stages/compose/text-renderers/word-fancy-renderer.js +135 -0
- package/dist/stages/compose/text-renderers/word-fancy-renderer.js.map +1 -0
- package/dist/stages/compose/text-utils/locale-detector.js +16 -0
- package/dist/stages/compose/text-utils/locale-detector.js.map +1 -0
- package/dist/stages/compose/text-utils/text-metrics.js +21 -0
- package/dist/stages/compose/text-utils/text-metrics.js.map +1 -0
- package/dist/stages/compose/text-utils/text-wrapper.js +225 -0
- package/dist/stages/compose/text-utils/text-wrapper.js.map +1 -0
- package/dist/stages/compose/types.d.ts +2 -1
- package/dist/stages/compose/types.d.ts.map +1 -1
- package/dist/stages/decode/BaseDecoder.js +0 -3
- package/dist/stages/decode/BaseDecoder.js.map +1 -1
- package/dist/stages/demux/MP4Demuxer.d.ts +5 -0
- package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
- package/dist/stages/demux/MP4Demuxer.js +281 -0
- package/dist/stages/demux/MP4Demuxer.js.map +1 -0
- package/dist/stages/demux/MP4IndexParser.d.ts +71 -0
- package/dist/stages/demux/MP4IndexParser.d.ts.map +1 -0
- package/dist/stages/demux/MP4IndexParser.js +416 -0
- package/dist/stages/demux/MP4IndexParser.js.map +1 -0
- package/dist/stages/demux/types.d.ts +48 -0
- package/dist/stages/demux/types.d.ts.map +1 -1
- package/dist/stages/encode/index.d.ts +0 -1
- package/dist/stages/encode/index.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts +44 -2
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +281 -37
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/stages/load/TaskManager.d.ts +6 -2
- package/dist/stages/load/TaskManager.d.ts.map +1 -1
- package/dist/stages/load/TaskManager.js +27 -4
- package/dist/stages/load/TaskManager.js.map +1 -1
- package/dist/stages/load/types.d.ts +7 -0
- package/dist/stages/load/types.d.ts.map +1 -1
- package/dist/stages/mux/MP4Muxer.d.ts +2 -2
- package/dist/stages/mux/MP4Muxer.d.ts.map +1 -1
- package/dist/stages/mux/MP4Muxer.js +24 -13
- package/dist/stages/mux/MP4Muxer.js.map +1 -1
- package/dist/stages/mux/MuxManager.d.ts +10 -21
- package/dist/stages/mux/MuxManager.d.ts.map +1 -1
- package/dist/stages/mux/MuxManager.js +21 -162
- package/dist/stages/mux/MuxManager.js.map +1 -1
- package/dist/stages/mux/index.d.ts +0 -1
- package/dist/stages/mux/index.d.ts.map +1 -1
- package/dist/utils/binary-search.d.ts +12 -4
- package/dist/utils/binary-search.d.ts.map +1 -1
- package/dist/utils/binary-search.js +52 -6
- package/dist/utils/binary-search.js.map +1 -1
- package/dist/workers/{BaseDecoder.BWYu1W0B.js → BaseDecoder.CTW-vr29.js} +1 -4
- package/dist/workers/BaseDecoder.CTW-vr29.js.map +1 -0
- package/dist/workers/{MP4Demuxer.CFHDkPYc.js → MP4Demuxer.BEa6PLJm.js} +10 -3
- package/dist/workers/{MP4Demuxer.CFHDkPYc.js.map → MP4Demuxer.BEa6PLJm.js.map} +1 -1
- package/dist/workers/stages/compose/{video-compose.worker.M5uomNVr.js → video-compose.worker.DHQ8B105.js} +260 -83
- package/dist/workers/stages/compose/video-compose.worker.DHQ8B105.js.map +1 -0
- package/dist/workers/stages/decode/{audio-decode.worker.DnS17GD9.js → audio-decode.worker.CP8bXXa4.js} +2 -2
- package/dist/workers/stages/decode/{audio-decode.worker.DnS17GD9.js.map → audio-decode.worker.CP8bXXa4.js.map} +1 -1
- package/dist/workers/stages/decode/{video-decode.worker.BEYsjOXp.js → video-decode.worker.BIspTxgV.js} +2 -2
- package/dist/workers/stages/decode/{video-decode.worker.BEYsjOXp.js.map → video-decode.worker.BIspTxgV.js.map} +1 -1
- package/dist/workers/stages/demux/{audio-demux.worker.BTFPcY7P.js → audio-demux.worker._VRQdLdv.js} +2 -2
- package/dist/workers/stages/demux/{audio-demux.worker.BTFPcY7P.js.map → audio-demux.worker._VRQdLdv.js.map} +1 -1
- package/dist/workers/stages/demux/{video-demux.worker.D_WeHPkt.js → video-demux.worker.CSkxGtmx.js} +3 -19
- package/dist/workers/stages/demux/video-demux.worker.CSkxGtmx.js.map +1 -0
- package/dist/workers/worker-manifest.json +5 -5
- package/package.json +1 -1
- package/dist/cache/l2/IndexedDBStore.js.map +0 -1
- package/dist/cache/l2/OPFSStore.js +0 -131
- package/dist/cache/l2/OPFSStore.js.map +0 -1
- package/dist/controllers/PreRenderService.d.ts +0 -59
- package/dist/controllers/PreRenderService.d.ts.map +0 -1
- package/dist/controllers/PreRenderService.js +0 -185
- package/dist/controllers/PreRenderService.js.map +0 -1
- package/dist/controllers/PreRenderTaskQueue.d.ts +0 -21
- package/dist/controllers/PreRenderTaskQueue.d.ts.map +0 -1
- package/dist/orchestrator/ClipSessionManager.d.ts +0 -70
- package/dist/orchestrator/ClipSessionManager.d.ts.map +0 -1
- package/dist/orchestrator/ClipSessionManager.js +0 -158
- package/dist/orchestrator/ClipSessionManager.js.map +0 -1
- package/dist/stages/decode/AudioChunkDecoder.js +0 -169
- package/dist/stages/decode/AudioChunkDecoder.js.map +0 -1
- package/dist/stages/encode/ClipEncoderManager.d.ts +0 -64
- package/dist/stages/encode/ClipEncoderManager.d.ts.map +0 -1
- package/dist/stages/mux/OPFSWriter.d.ts +0 -46
- package/dist/stages/mux/OPFSWriter.d.ts.map +0 -1
- package/dist/utils/BackpressureAdapter.d.ts +0 -26
- package/dist/utils/BackpressureAdapter.d.ts.map +0 -1
- package/dist/utils/time-utils.js +0 -45
- package/dist/utils/time-utils.js.map +0 -1
- package/dist/workers/BaseDecoder.BWYu1W0B.js.map +0 -1
- package/dist/workers/stages/compose/video-compose.worker.M5uomNVr.js.map +0 -1
- package/dist/workers/stages/demux/video-demux.worker.D_WeHPkt.js.map +0 -1
|
@@ -1,17 +1,4 @@
|
|
|
1
1
|
import { W as WorkerChannel, a as WorkerMessageType, b as WorkerState } from "../../WorkerChannel.CE5euh3R.js";
|
|
2
|
-
const MICROSECONDS_PER_SECOND = 1e6;
|
|
3
|
-
const DEFAULT_FPS = 30;
|
|
4
|
-
function normalizeFps(value) {
|
|
5
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
6
|
-
return DEFAULT_FPS;
|
|
7
|
-
}
|
|
8
|
-
return value;
|
|
9
|
-
}
|
|
10
|
-
function frameDurationFromFps(fps) {
|
|
11
|
-
const normalized = normalizeFps(fps);
|
|
12
|
-
const duration = MICROSECONDS_PER_SECOND / normalized;
|
|
13
|
-
return Math.max(Math.round(duration), 1);
|
|
14
|
-
}
|
|
15
2
|
function measureTextWidth(ctx, text, fontSize, fontFamily, fontWeight = 400) {
|
|
16
3
|
ctx.save();
|
|
17
4
|
ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
@@ -1456,7 +1443,9 @@ class FilterProcessor {
|
|
|
1456
1443
|
const closeLayerFrame = (layer) => {
|
|
1457
1444
|
if (layer.type === "video") {
|
|
1458
1445
|
const vf = layer.videoFrame;
|
|
1459
|
-
vf?.close
|
|
1446
|
+
if (vf?.close) {
|
|
1447
|
+
vf.close();
|
|
1448
|
+
}
|
|
1460
1449
|
}
|
|
1461
1450
|
};
|
|
1462
1451
|
class VideoComposer {
|
|
@@ -1466,32 +1455,42 @@ class VideoComposer {
|
|
|
1466
1455
|
layerRenderer;
|
|
1467
1456
|
transitionProcessor;
|
|
1468
1457
|
filterProcessor;
|
|
1469
|
-
|
|
1458
|
+
frameDurationUs;
|
|
1459
|
+
// Cached frame duration
|
|
1470
1460
|
constructor(config) {
|
|
1471
1461
|
this.config = this.applyDefaults(config);
|
|
1472
|
-
this.
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1462
|
+
this.frameDurationUs = Math.round(1e6 / this.config.fps);
|
|
1463
|
+
if (config.externalCanvas) {
|
|
1464
|
+
this.canvas = config.externalCanvas;
|
|
1465
|
+
this.ctx = this.canvas.getContext("2d", {
|
|
1466
|
+
alpha: true,
|
|
1467
|
+
desynchronized: true,
|
|
1468
|
+
willReadFrequently: false,
|
|
1469
|
+
colorSpace: "srgb"
|
|
1470
|
+
});
|
|
1471
|
+
} else {
|
|
1472
|
+
this.canvas = new OffscreenCanvas(this.config.width, this.config.height);
|
|
1473
|
+
this.ctx = this.canvas.getContext("2d", {
|
|
1474
|
+
alpha: true,
|
|
1475
|
+
desynchronized: true,
|
|
1476
|
+
willReadFrequently: false,
|
|
1477
|
+
colorSpace: "srgb"
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
if (!this.ctx) {
|
|
1480
1481
|
throw new Error("Failed to create 2D rendering context");
|
|
1481
1482
|
}
|
|
1482
|
-
this.ctx = ctx;
|
|
1483
1483
|
this.ctx.imageSmoothingEnabled = this.config.enableSmoothing;
|
|
1484
1484
|
this.loadFonts();
|
|
1485
1485
|
this.ctx.imageSmoothingQuality = "high";
|
|
1486
1486
|
this.layerRenderer = new LayerRenderer(
|
|
1487
|
-
ctx,
|
|
1487
|
+
this.ctx,
|
|
1488
1488
|
this.config.width,
|
|
1489
1489
|
this.config.height,
|
|
1490
1490
|
this.config.fps
|
|
1491
1491
|
);
|
|
1492
1492
|
this.transitionProcessor = new TransitionProcessor(this.config.width, this.config.height);
|
|
1493
1493
|
this.filterProcessor = new FilterProcessor();
|
|
1494
|
-
this.timelineContext = this.config.timeline;
|
|
1495
1494
|
}
|
|
1496
1495
|
applyDefaults(config) {
|
|
1497
1496
|
return {
|
|
@@ -1513,21 +1512,18 @@ class VideoComposer {
|
|
|
1513
1512
|
clipDurationUs: Infinity,
|
|
1514
1513
|
compositionFps: 30
|
|
1515
1514
|
},
|
|
1516
|
-
fonts: config.fonts ?? []
|
|
1515
|
+
fonts: config.fonts ?? [],
|
|
1516
|
+
externalCanvas: config.externalCanvas
|
|
1517
1517
|
};
|
|
1518
1518
|
}
|
|
1519
|
-
createStreams(
|
|
1520
|
-
if (instruction?.baseConfig.timeline) {
|
|
1521
|
-
this.timelineContext = instruction.baseConfig.timeline;
|
|
1522
|
-
}
|
|
1519
|
+
createStreams(_instruction) {
|
|
1523
1520
|
const stream = new TransformStream(
|
|
1524
1521
|
{
|
|
1525
1522
|
transform: async (request, controller) => {
|
|
1526
1523
|
const result = await this.composeFrame(request);
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
}, 1e3);
|
|
1524
|
+
if (result.frame) {
|
|
1525
|
+
controller.enqueue(result.frame);
|
|
1526
|
+
}
|
|
1531
1527
|
},
|
|
1532
1528
|
flush: async () => {
|
|
1533
1529
|
this.filterProcessor.clearCache();
|
|
@@ -1560,13 +1556,27 @@ class VideoComposer {
|
|
|
1560
1556
|
continue;
|
|
1561
1557
|
}
|
|
1562
1558
|
try {
|
|
1563
|
-
if (layer.
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1559
|
+
if (layer.rcFrame) {
|
|
1560
|
+
await layer.rcFrame.use(async (frame2) => {
|
|
1561
|
+
layer.videoFrame = frame2;
|
|
1562
|
+
if (layer.filters && layer.filters.length > 0) {
|
|
1563
|
+
this.ctx.save();
|
|
1564
|
+
this.filterProcessor.applyFilters(this.ctx, layer.filters);
|
|
1565
|
+
}
|
|
1566
|
+
await this.layerRenderer.renderLayer(layer);
|
|
1567
|
+
if (layer.filters && layer.filters.length > 0) {
|
|
1568
|
+
this.ctx.restore();
|
|
1569
|
+
}
|
|
1570
|
+
});
|
|
1571
|
+
} else {
|
|
1572
|
+
if (layer.filters && layer.filters.length > 0) {
|
|
1573
|
+
this.ctx.save();
|
|
1574
|
+
this.filterProcessor.applyFilters(this.ctx, layer.filters);
|
|
1575
|
+
}
|
|
1576
|
+
await this.layerRenderer.renderLayer(layer);
|
|
1577
|
+
if (layer.filters && layer.filters.length > 0) {
|
|
1578
|
+
this.ctx.restore();
|
|
1579
|
+
}
|
|
1570
1580
|
}
|
|
1571
1581
|
} catch (error) {
|
|
1572
1582
|
console.error("[VideoComposer] composeFrame error: ", error);
|
|
@@ -1576,7 +1586,10 @@ class VideoComposer {
|
|
|
1576
1586
|
if (request.transition) {
|
|
1577
1587
|
this.ctx.restore();
|
|
1578
1588
|
}
|
|
1579
|
-
|
|
1589
|
+
let frame = null;
|
|
1590
|
+
if (!this.config.externalCanvas) {
|
|
1591
|
+
frame = await this.createOutputFrame(request.timeUs);
|
|
1592
|
+
}
|
|
1580
1593
|
return {
|
|
1581
1594
|
frame,
|
|
1582
1595
|
timeUs: request.timeUs
|
|
@@ -1602,10 +1615,10 @@ class VideoComposer {
|
|
|
1602
1615
|
// return [...layers].sort((a, b) => a.zIndex - b.zIndex);
|
|
1603
1616
|
// }
|
|
1604
1617
|
async createOutputFrame(timeUs) {
|
|
1605
|
-
const duration = frameDurationFromFps(this.timelineContext.compositionFps);
|
|
1606
1618
|
const frame = new VideoFrame(this.canvas, {
|
|
1607
1619
|
timestamp: timeUs,
|
|
1608
|
-
duration,
|
|
1620
|
+
duration: this.frameDurationUs,
|
|
1621
|
+
// Use cached duration
|
|
1609
1622
|
alpha: "discard",
|
|
1610
1623
|
visibleRect: { x: 0, y: 0, width: this.canvas.width, height: this.canvas.height }
|
|
1611
1624
|
});
|
|
@@ -1629,6 +1642,9 @@ class VideoComposer {
|
|
|
1629
1642
|
}
|
|
1630
1643
|
updateConfig(config) {
|
|
1631
1644
|
Object.assign(this.config, this.applyDefaults({ ...this.config, ...config }));
|
|
1645
|
+
if (config.fps !== void 0) {
|
|
1646
|
+
this.frameDurationUs = Math.round(1e6 / this.config.fps);
|
|
1647
|
+
}
|
|
1632
1648
|
if (config.width || config.height) {
|
|
1633
1649
|
this.canvas.width = this.config.width;
|
|
1634
1650
|
this.canvas.height = this.config.height;
|
|
@@ -1638,9 +1654,6 @@ class VideoComposer {
|
|
|
1638
1654
|
if (config.enableSmoothing !== void 0) {
|
|
1639
1655
|
this.ctx.imageSmoothingEnabled = this.config.enableSmoothing;
|
|
1640
1656
|
}
|
|
1641
|
-
if (config.timeline) {
|
|
1642
|
-
this.timelineContext = config.timeline;
|
|
1643
|
-
}
|
|
1644
1657
|
if (config.fonts) {
|
|
1645
1658
|
this.loadFonts();
|
|
1646
1659
|
}
|
|
@@ -1649,6 +1662,19 @@ class VideoComposer {
|
|
|
1649
1662
|
this.filterProcessor.clearCache();
|
|
1650
1663
|
}
|
|
1651
1664
|
}
|
|
1665
|
+
const MICROSECONDS_PER_SECOND = 1e6;
|
|
1666
|
+
const DEFAULT_FPS = 30;
|
|
1667
|
+
function normalizeFps(value) {
|
|
1668
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
1669
|
+
return DEFAULT_FPS;
|
|
1670
|
+
}
|
|
1671
|
+
return value;
|
|
1672
|
+
}
|
|
1673
|
+
function frameDurationFromFps(fps) {
|
|
1674
|
+
const normalized = normalizeFps(fps);
|
|
1675
|
+
const duration = MICROSECONDS_PER_SECOND / normalized;
|
|
1676
|
+
return Math.max(Math.round(duration), 1);
|
|
1677
|
+
}
|
|
1652
1678
|
function interpolateKeyframes(keyframes, timeUs) {
|
|
1653
1679
|
const defaultTransform = {
|
|
1654
1680
|
x: 0,
|
|
@@ -1727,6 +1753,183 @@ function applyEasing(t, easing) {
|
|
|
1727
1753
|
return t;
|
|
1728
1754
|
}
|
|
1729
1755
|
}
|
|
1756
|
+
class FrameRateConverter {
|
|
1757
|
+
clipDurationUs;
|
|
1758
|
+
frameDurationUs;
|
|
1759
|
+
// State for frame processing
|
|
1760
|
+
targetFrameIndex = 0;
|
|
1761
|
+
targetFrameTimeUs = 0;
|
|
1762
|
+
sourceFrameBuffer = [];
|
|
1763
|
+
constructor(targetFps, clipDurationUs) {
|
|
1764
|
+
if (targetFps <= 0) {
|
|
1765
|
+
throw new Error(`Invalid target fps: ${targetFps}`);
|
|
1766
|
+
}
|
|
1767
|
+
if (clipDurationUs <= 0) {
|
|
1768
|
+
throw new Error(`Invalid clip duration: ${clipDurationUs}`);
|
|
1769
|
+
}
|
|
1770
|
+
this.clipDurationUs = clipDurationUs;
|
|
1771
|
+
this.frameDurationUs = Math.round(1e6 / targetFps);
|
|
1772
|
+
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Create a TransformStream that converts VFR frames to CFR frames
|
|
1775
|
+
*/
|
|
1776
|
+
createStream() {
|
|
1777
|
+
return new TransformStream({
|
|
1778
|
+
start: () => {
|
|
1779
|
+
this.targetFrameIndex = 0;
|
|
1780
|
+
this.targetFrameTimeUs = 0;
|
|
1781
|
+
this.sourceFrameBuffer = [];
|
|
1782
|
+
this.sourceFrameCount = 0;
|
|
1783
|
+
this.outputFrameCount = 0;
|
|
1784
|
+
},
|
|
1785
|
+
transform: (sourceFrame, controller) => {
|
|
1786
|
+
this.processSourceFrame(sourceFrame, controller);
|
|
1787
|
+
},
|
|
1788
|
+
flush: (controller) => {
|
|
1789
|
+
this.flushRemainingFrames(controller);
|
|
1790
|
+
}
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
sourceFrameCount = 0;
|
|
1794
|
+
outputFrameCount = 0;
|
|
1795
|
+
/**
|
|
1796
|
+
* Process incoming source frame and output target frames
|
|
1797
|
+
*/
|
|
1798
|
+
processSourceFrame(sourceFrame, controller) {
|
|
1799
|
+
this.sourceFrameBuffer.push(sourceFrame);
|
|
1800
|
+
this.sourceFrameCount++;
|
|
1801
|
+
while (this.targetFrameTimeUs < this.clipDurationUs) {
|
|
1802
|
+
const closestFrame = this.findClosestFrame(this.targetFrameTimeUs);
|
|
1803
|
+
if (!closestFrame) {
|
|
1804
|
+
break;
|
|
1805
|
+
}
|
|
1806
|
+
if (this.shouldWaitForNextFrame(closestFrame)) {
|
|
1807
|
+
break;
|
|
1808
|
+
}
|
|
1809
|
+
try {
|
|
1810
|
+
const targetFrame = new VideoFrame(closestFrame, {
|
|
1811
|
+
timestamp: this.targetFrameTimeUs,
|
|
1812
|
+
duration: this.frameDurationUs
|
|
1813
|
+
});
|
|
1814
|
+
controller.enqueue(targetFrame);
|
|
1815
|
+
this.outputFrameCount++;
|
|
1816
|
+
this.cleanupOldFrames(this.targetFrameTimeUs);
|
|
1817
|
+
this.targetFrameIndex++;
|
|
1818
|
+
this.targetFrameTimeUs = this.targetFrameIndex * this.frameDurationUs;
|
|
1819
|
+
} catch (_error) {
|
|
1820
|
+
console.error("[FrameRateConverter] Failed to create target frame:", _error);
|
|
1821
|
+
this.targetFrameIndex++;
|
|
1822
|
+
this.targetFrameTimeUs = this.targetFrameIndex * this.frameDurationUs;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Flush remaining target frames at end of stream
|
|
1828
|
+
*/
|
|
1829
|
+
flushRemainingFrames(controller) {
|
|
1830
|
+
while (this.sourceFrameBuffer.length > 0) {
|
|
1831
|
+
const closestFrame = this.findClosestFrame(this.targetFrameTimeUs);
|
|
1832
|
+
if (!closestFrame) {
|
|
1833
|
+
break;
|
|
1834
|
+
}
|
|
1835
|
+
if (this.targetFrameTimeUs >= this.clipDurationUs) {
|
|
1836
|
+
break;
|
|
1837
|
+
}
|
|
1838
|
+
try {
|
|
1839
|
+
const targetFrame = new VideoFrame(closestFrame, {
|
|
1840
|
+
timestamp: this.targetFrameTimeUs,
|
|
1841
|
+
duration: this.frameDurationUs
|
|
1842
|
+
});
|
|
1843
|
+
controller.enqueue(targetFrame);
|
|
1844
|
+
this.cleanupOldFrames(this.targetFrameTimeUs);
|
|
1845
|
+
this.targetFrameIndex++;
|
|
1846
|
+
this.targetFrameTimeUs = this.targetFrameIndex * this.frameDurationUs;
|
|
1847
|
+
} catch (_error) {
|
|
1848
|
+
console.error("[FrameRateConverter] Failed to create target frame in flush:", _error);
|
|
1849
|
+
break;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
for (const frame of this.sourceFrameBuffer) {
|
|
1853
|
+
try {
|
|
1854
|
+
frame.close();
|
|
1855
|
+
} catch {
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
this.sourceFrameBuffer = [];
|
|
1859
|
+
}
|
|
1860
|
+
/**
|
|
1861
|
+
* Find the source frame closest to target time
|
|
1862
|
+
*/
|
|
1863
|
+
findClosestFrame(targetTimeUs) {
|
|
1864
|
+
if (this.sourceFrameBuffer.length === 0) {
|
|
1865
|
+
return null;
|
|
1866
|
+
}
|
|
1867
|
+
let closestFrame = this.sourceFrameBuffer[0];
|
|
1868
|
+
let minError = Math.abs((closestFrame?.timestamp ?? 0) - targetTimeUs);
|
|
1869
|
+
for (const frame of this.sourceFrameBuffer) {
|
|
1870
|
+
const error = Math.abs((frame.timestamp ?? 0) - targetTimeUs);
|
|
1871
|
+
if (error < minError) {
|
|
1872
|
+
minError = error;
|
|
1873
|
+
closestFrame = frame;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
return closestFrame ?? null;
|
|
1877
|
+
}
|
|
1878
|
+
/**
|
|
1879
|
+
* Check if we should wait for next source frame before outputting
|
|
1880
|
+
* Returns true if:
|
|
1881
|
+
* - We only have 1 frame in buffer
|
|
1882
|
+
* - The closest frame is before target time
|
|
1883
|
+
* - We might get a better match from next frame
|
|
1884
|
+
*/
|
|
1885
|
+
shouldWaitForNextFrame(closestFrame) {
|
|
1886
|
+
if (this.sourceFrameBuffer.length <= 1) {
|
|
1887
|
+
const frameTimestamp = closestFrame.timestamp ?? 0;
|
|
1888
|
+
if (frameTimestamp < this.targetFrameTimeUs) {
|
|
1889
|
+
return true;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
return false;
|
|
1893
|
+
}
|
|
1894
|
+
/**
|
|
1895
|
+
* Clean up source frames that are no longer needed
|
|
1896
|
+
* Keep frames that might be needed for future target frames
|
|
1897
|
+
*/
|
|
1898
|
+
cleanupOldFrames(currentTargetTimeUs) {
|
|
1899
|
+
const nextTargetTimeUs = currentTargetTimeUs + this.frameDurationUs;
|
|
1900
|
+
const previousTargetTimeUs = currentTargetTimeUs - this.frameDurationUs;
|
|
1901
|
+
let removeCount = 0;
|
|
1902
|
+
for (let i = 0; i < this.sourceFrameBuffer.length; i++) {
|
|
1903
|
+
const frame = this.sourceFrameBuffer[i];
|
|
1904
|
+
if (!frame) continue;
|
|
1905
|
+
const frameTimestamp = frame.timestamp ?? 0;
|
|
1906
|
+
const isNeededForNext = Math.abs(frameTimestamp - nextTargetTimeUs) < Math.abs(frameTimestamp - previousTargetTimeUs);
|
|
1907
|
+
if (frameTimestamp < previousTargetTimeUs && !isNeededForNext) {
|
|
1908
|
+
try {
|
|
1909
|
+
frame.close();
|
|
1910
|
+
} catch {
|
|
1911
|
+
}
|
|
1912
|
+
removeCount++;
|
|
1913
|
+
} else {
|
|
1914
|
+
break;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
if (removeCount > 0) {
|
|
1918
|
+
this.sourceFrameBuffer.splice(0, removeCount);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Get current conversion state (for debugging)
|
|
1923
|
+
*/
|
|
1924
|
+
getState() {
|
|
1925
|
+
return {
|
|
1926
|
+
targetFrameIndex: this.targetFrameIndex,
|
|
1927
|
+
targetFrameTimeUs: this.targetFrameTimeUs,
|
|
1928
|
+
bufferSize: this.sourceFrameBuffer.length,
|
|
1929
|
+
frameDurationUs: this.frameDurationUs
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1730
1933
|
function resolveActiveLayers(layers, timestamp) {
|
|
1731
1934
|
return layers.filter((layer) => {
|
|
1732
1935
|
if (!layer.payload.attachmentId) {
|
|
@@ -1842,7 +2045,6 @@ class VideoComposeWorker {
|
|
|
1842
2045
|
downstreamPort = null;
|
|
1843
2046
|
upstreamPort = null;
|
|
1844
2047
|
instructions = null;
|
|
1845
|
-
streamState = null;
|
|
1846
2048
|
imageMap = /* @__PURE__ */ new Map();
|
|
1847
2049
|
constructor() {
|
|
1848
2050
|
this.channel = new WorkerChannel(self, {
|
|
@@ -1925,7 +2127,13 @@ class VideoComposeWorker {
|
|
|
1925
2127
|
console.warn("[VideoComposeWorker] No instructions installed");
|
|
1926
2128
|
return;
|
|
1927
2129
|
}
|
|
1928
|
-
const
|
|
2130
|
+
const timeline = this.instructions.baseConfig.timeline;
|
|
2131
|
+
const fpsConverter = new FrameRateConverter(
|
|
2132
|
+
timeline?.compositionFps ?? 30,
|
|
2133
|
+
timeline?.clipDurationUs ?? Infinity
|
|
2134
|
+
);
|
|
2135
|
+
const cfrStream = stream.pipeThrough(fpsConverter.createStream());
|
|
2136
|
+
const filteredStream = cfrStream.pipeThrough(
|
|
1929
2137
|
new TransformStream({
|
|
1930
2138
|
transform: (frame, controller) => {
|
|
1931
2139
|
try {
|
|
@@ -2004,7 +2212,6 @@ class VideoComposeWorker {
|
|
|
2004
2212
|
this.imageMap.forEach((bitmap) => bitmap.close());
|
|
2005
2213
|
this.imageMap.clear();
|
|
2006
2214
|
this.instructions = null;
|
|
2007
|
-
this.streamState = null;
|
|
2008
2215
|
this.channel.state = WorkerState.Disposed;
|
|
2009
2216
|
return { success: true };
|
|
2010
2217
|
}
|
|
@@ -2045,7 +2252,6 @@ class VideoComposeWorker {
|
|
|
2045
2252
|
}
|
|
2046
2253
|
async handleDisposeClip() {
|
|
2047
2254
|
this.instructions = null;
|
|
2048
|
-
this.streamState = null;
|
|
2049
2255
|
this.downstreamPort?.close();
|
|
2050
2256
|
this.upstreamPort?.close();
|
|
2051
2257
|
this.downstreamPort = null;
|
|
@@ -2115,7 +2321,7 @@ class VideoComposeWorker {
|
|
|
2115
2321
|
}
|
|
2116
2322
|
}
|
|
2117
2323
|
buildComposeRequest(instruction, frame) {
|
|
2118
|
-
const clipRelativeTime =
|
|
2324
|
+
const clipRelativeTime = frame.timestamp ?? 0;
|
|
2119
2325
|
const clipDurationUs = instruction.baseConfig.timeline?.clipDurationUs ?? Infinity;
|
|
2120
2326
|
if (clipRelativeTime < 0 || clipRelativeTime >= clipDurationUs) {
|
|
2121
2327
|
return null;
|
|
@@ -2159,35 +2365,6 @@ class VideoComposeWorker {
|
|
|
2159
2365
|
direction: entry.params.payload?.direction
|
|
2160
2366
|
};
|
|
2161
2367
|
}
|
|
2162
|
-
computeTimelineTimestamp(frame, config) {
|
|
2163
|
-
if (!this.streamState) {
|
|
2164
|
-
this.streamState = {
|
|
2165
|
-
lastSourceTimestamp: null,
|
|
2166
|
-
nextFrameIndex: 0
|
|
2167
|
-
};
|
|
2168
|
-
}
|
|
2169
|
-
const timeline = config.timeline;
|
|
2170
|
-
if (!timeline) {
|
|
2171
|
-
const ts = frame.timestamp ?? 0;
|
|
2172
|
-
this.streamState.lastSourceTimestamp = frame.timestamp ?? null;
|
|
2173
|
-
return ts;
|
|
2174
|
-
}
|
|
2175
|
-
const { compositionFps } = timeline;
|
|
2176
|
-
const sourceTimestamp = frame.timestamp ?? null;
|
|
2177
|
-
if (sourceTimestamp !== null && this.streamState.lastSourceTimestamp !== null && sourceTimestamp < this.streamState.lastSourceTimestamp) {
|
|
2178
|
-
this.streamState.nextFrameIndex = 0;
|
|
2179
|
-
}
|
|
2180
|
-
const frameDuration = frameDurationFromFps(compositionFps);
|
|
2181
|
-
let frameIndex = this.streamState.nextFrameIndex;
|
|
2182
|
-
if (sourceTimestamp !== null) {
|
|
2183
|
-
const approxIndex = Math.round(sourceTimestamp / frameDuration);
|
|
2184
|
-
frameIndex = Math.max(frameIndex, approxIndex);
|
|
2185
|
-
}
|
|
2186
|
-
const relativeTimeUs = frameIndex * frameDuration;
|
|
2187
|
-
this.streamState.nextFrameIndex = frameIndex + 1;
|
|
2188
|
-
this.streamState.lastSourceTimestamp = sourceTimestamp;
|
|
2189
|
-
return relativeTimeUs;
|
|
2190
|
-
}
|
|
2191
2368
|
}
|
|
2192
2369
|
const worker = new VideoComposeWorker();
|
|
2193
2370
|
self.addEventListener("beforeunload", () => {
|
|
@@ -2198,4 +2375,4 @@ export {
|
|
|
2198
2375
|
VideoComposeWorker,
|
|
2199
2376
|
videoCompose_worker as default
|
|
2200
2377
|
};
|
|
2201
|
-
//# sourceMappingURL=video-compose.worker.
|
|
2378
|
+
//# sourceMappingURL=video-compose.worker.DHQ8B105.js.map
|