@meframe/core 0.0.2 → 0.0.4
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 +6 -4
- package/dist/Meframe.js.map +1 -1
- package/dist/cache/CacheManager.d.ts +2 -2
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +4 -3
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/l1/VideoL1Cache.d.ts +2 -2
- package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
- package/dist/cache/l1/VideoL1Cache.js +13 -8
- package/dist/cache/l1/VideoL1Cache.js.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +3 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/types.d.ts +6 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.d.ts +7 -8
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +56 -76
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/controllers/types.d.ts +2 -3
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/event/events.d.ts +1 -4
- package/dist/event/events.d.ts.map +1 -1
- package/dist/event/events.js.map +1 -1
- package/dist/model/CompositionModel.d.ts +1 -0
- package/dist/model/CompositionModel.d.ts.map +1 -1
- package/dist/model/CompositionModel.js +2 -0
- package/dist/model/CompositionModel.js.map +1 -1
- package/dist/model/patch.d.ts +6 -2
- package/dist/model/patch.d.ts.map +1 -1
- package/dist/model/patch.js +76 -2
- package/dist/model/patch.js.map +1 -1
- package/dist/model/types.d.ts +1 -0
- package/dist/model/types.d.ts.map +1 -1
- package/dist/orchestrator/CompositionPlanner.d.ts +8 -7
- package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
- package/dist/orchestrator/CompositionPlanner.js +33 -56
- package/dist/orchestrator/CompositionPlanner.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts +0 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +40 -19
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts +3 -5
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +66 -69
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/orchestrator/types.d.ts +2 -0
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/stages/compose/GlobalAudioSession.d.ts +6 -0
- package/dist/stages/compose/GlobalAudioSession.d.ts.map +1 -1
- package/dist/stages/compose/GlobalAudioSession.js +17 -1
- package/dist/stages/compose/GlobalAudioSession.js.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/stages/load/ResourceLoader.d.ts +22 -1
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +71 -25
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/stages/load/TaskManager.d.ts +1 -1
- package/dist/stages/load/TaskManager.d.ts.map +1 -1
- package/dist/stages/load/TaskManager.js +3 -2
- package/dist/stages/load/TaskManager.js.map +1 -1
- package/dist/stages/load/types.d.ts +2 -0
- package/dist/stages/load/types.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 +9 -0
- package/dist/worker/WorkerPool.d.ts.map +1 -1
- package/dist/worker/WorkerPool.js +32 -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} +120 -562
- 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/controllers/PreviewHandle.d.ts +0 -25
- package/dist/controllers/PreviewHandle.d.ts.map +0 -1
- package/dist/controllers/PreviewHandle.js +0 -45
- package/dist/controllers/PreviewHandle.js.map +0 -1
- package/dist/model/dirty-range.js +0 -220
- package/dist/model/dirty-range.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
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"audio-compose.worker2.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VideoComposeWorker - Visual composition in the pipeline
|
|
3
|
-
* Receives decoded video frames and outputs composed frames
|
|
4
|
-
*
|
|
5
|
-
* Pipeline: DecodeWorker → VideoComposeWorker → EncodeWorker
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Multi-layer composition with Canvas2D/WebGL
|
|
9
|
-
* - Transition effects and filters
|
|
10
|
-
* - Text and image overlay support
|
|
11
|
-
* - Stream-based processing with configurable backpressure
|
|
12
|
-
*/
|
|
13
|
-
export declare class VideoComposeWorker {
|
|
14
|
-
private channel;
|
|
15
|
-
private composer;
|
|
16
|
-
private composeStream;
|
|
17
|
-
private downstreamPorts;
|
|
18
|
-
private upstreamPorts;
|
|
19
|
-
private instructionRegistry;
|
|
20
|
-
private pendingReplay;
|
|
21
|
-
private streamState;
|
|
22
|
-
constructor();
|
|
23
|
-
private setupHandlers;
|
|
24
|
-
/**
|
|
25
|
-
* Unified connect handler used by stream pipeline
|
|
26
|
-
*/
|
|
27
|
-
private handleConnect;
|
|
28
|
-
/**
|
|
29
|
-
* Configure composer
|
|
30
|
-
* According to docs/impl/14-config, only reinitialize when initial=true
|
|
31
|
-
*/
|
|
32
|
-
private handleConfigure;
|
|
33
|
-
private handleReceiveStream;
|
|
34
|
-
/**
|
|
35
|
-
* Flush the composition pipeline
|
|
36
|
-
*/
|
|
37
|
-
private handleFlush;
|
|
38
|
-
/**
|
|
39
|
-
* Get composer statistics
|
|
40
|
-
*/
|
|
41
|
-
private handleGetStats;
|
|
42
|
-
/**
|
|
43
|
-
* Dispose worker and cleanup resources
|
|
44
|
-
*/
|
|
45
|
-
private handleDispose;
|
|
46
|
-
private handleInstallInstructions;
|
|
47
|
-
private handleSyncClip;
|
|
48
|
-
private handleDisposeClip;
|
|
49
|
-
/**
|
|
50
|
-
* Check if frame should be skipped (outside dirty range)
|
|
51
|
-
* Returns true if frame is NOT in the dirty range and should use cached version
|
|
52
|
-
*/
|
|
53
|
-
private shouldSkipFrame;
|
|
54
|
-
private buildComposeRequest;
|
|
55
|
-
private static buildTransition;
|
|
56
|
-
private computeTimelineTimestamp;
|
|
57
|
-
}
|
|
58
|
-
declare const _default: null;
|
|
59
|
-
export default _default;
|
|
60
|
-
//# sourceMappingURL=video-compose.worker.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"video-compose.worker.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/video-compose.worker.ts"],"names":[],"mappings":"AAmFA;;;;;;;;;;;GAWG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,aAAa,CAA4D;IAEjF,OAAO,CAAC,eAAe,CAAkC;IACzD,OAAO,CAAC,aAAa,CAAkC;IACvD,OAAO,CAAC,mBAAmB,CAAyC;IACpE,OAAO,CAAC,aAAa,CAA2E;IAChG,OAAO,CAAC,WAAW,CAAkC;;IAYrD,OAAO,CAAC,aAAa;IAarB;;OAEG;YACW,aAAa;IAqB3B;;;OAGG;YACW,eAAe;YAiDf,mBAAmB;IA0EjC;;OAEG;YACW,WAAW;IAezB;;OAEG;YACW,cAAc;IAY5B;;OAEG;YACW,aAAa;YAoBb,yBAAyB;YAWzB,cAAc;YAgBd,iBAAiB;IAW/B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAoBvB,OAAO,CAAC,mBAAmB;IAkC3B,OAAO,CAAC,MAAM,CAAC,eAAe;IAuB9B,OAAO,CAAC,wBAAwB;CA+EjC;;AAgBD,wBAAoB"}
|
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
import { WorkerChannel } from "../../worker/WorkerChannel.js";
|
|
2
|
-
import { WorkerMessageType, WorkerState } from "../../worker/types.js";
|
|
3
|
-
import { VideoComposer } from "./VideoComposer.js";
|
|
4
|
-
import { frameIndexFromTimestamp, quantizeTimestampToFrame, frameDurationFromFps } from "../../utils/time-utils.js";
|
|
5
|
-
function resolveActiveLayers(layers, timestamp, _frame) {
|
|
6
|
-
return layers.filter((layer) => {
|
|
7
|
-
if (layer.status !== "ready") {
|
|
8
|
-
return false;
|
|
9
|
-
}
|
|
10
|
-
return layer.activeRanges.some(
|
|
11
|
-
(range) => timestamp >= range.startUs && timestamp < range.endUs
|
|
12
|
-
);
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
function materializeLayer(layer, frame) {
|
|
16
|
-
const baseLayer = {
|
|
17
|
-
id: layer.layerId,
|
|
18
|
-
type: layer.type,
|
|
19
|
-
zIndex: layer.zIndex ?? 0,
|
|
20
|
-
visible: true,
|
|
21
|
-
opacity: layer.opacity ?? 1
|
|
22
|
-
};
|
|
23
|
-
if (layer.type === "video") {
|
|
24
|
-
return {
|
|
25
|
-
...baseLayer,
|
|
26
|
-
type: "video",
|
|
27
|
-
videoFrame: frame
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
if (layer.type === "text") {
|
|
31
|
-
const payload = layer.payload;
|
|
32
|
-
return {
|
|
33
|
-
...baseLayer,
|
|
34
|
-
type: "text",
|
|
35
|
-
text: payload.text,
|
|
36
|
-
fontFamily: payload.fontFamily,
|
|
37
|
-
fontSize: payload.fontSize,
|
|
38
|
-
fontWeight: payload.fontWeight,
|
|
39
|
-
color: payload.color,
|
|
40
|
-
strokeColor: payload.strokeColor,
|
|
41
|
-
strokeWidth: payload.strokeWidth,
|
|
42
|
-
lineHeight: payload.lineHeight,
|
|
43
|
-
textAlign: payload.align,
|
|
44
|
-
verticalAlign: "bottom"
|
|
45
|
-
// Subtitles positioned at bottom
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
if (layer.type === "image") {
|
|
49
|
-
const payload = layer.payload;
|
|
50
|
-
const source = payload.bitmapHandle ?? null;
|
|
51
|
-
if (source) {
|
|
52
|
-
return {
|
|
53
|
-
...baseLayer,
|
|
54
|
-
type: "image",
|
|
55
|
-
source
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return baseLayer;
|
|
60
|
-
}
|
|
61
|
-
class VideoComposeWorker {
|
|
62
|
-
channel;
|
|
63
|
-
composer = null;
|
|
64
|
-
composeStream = null;
|
|
65
|
-
downstreamPorts = /* @__PURE__ */ new Map();
|
|
66
|
-
upstreamPorts = /* @__PURE__ */ new Map();
|
|
67
|
-
instructionRegistry = /* @__PURE__ */ new Map();
|
|
68
|
-
pendingReplay = /* @__PURE__ */ new Map();
|
|
69
|
-
streamState = /* @__PURE__ */ new Map();
|
|
70
|
-
constructor() {
|
|
71
|
-
this.channel = new WorkerChannel(self, {
|
|
72
|
-
name: "VideoComposeWorker",
|
|
73
|
-
timeout: 3e4
|
|
74
|
-
});
|
|
75
|
-
this.setupHandlers();
|
|
76
|
-
}
|
|
77
|
-
setupHandlers() {
|
|
78
|
-
this.channel.registerHandler("configure", this.handleConfigure.bind(this));
|
|
79
|
-
this.channel.registerHandler("connect", this.handleConnect.bind(this));
|
|
80
|
-
this.channel.registerHandler("flush", this.handleFlush.bind(this));
|
|
81
|
-
this.channel.registerHandler("get_stats", this.handleGetStats.bind(this));
|
|
82
|
-
this.channel.registerHandler("install_instructions", this.handleInstallInstructions.bind(this));
|
|
83
|
-
this.channel.registerHandler("sync_clip", this.handleSyncClip.bind(this));
|
|
84
|
-
this.channel.registerHandler("dispose_clip", this.handleDisposeClip.bind(this));
|
|
85
|
-
this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Unified connect handler used by stream pipeline
|
|
89
|
-
*/
|
|
90
|
-
async handleConnect(payload) {
|
|
91
|
-
const { port, direction, clipId = "default" } = payload;
|
|
92
|
-
if (direction === "upstream") {
|
|
93
|
-
this.upstreamPorts.set(clipId, port);
|
|
94
|
-
const channel = new WorkerChannel(port, {
|
|
95
|
-
name: "VideoCompose-Decode",
|
|
96
|
-
timeout: 3e4
|
|
97
|
-
});
|
|
98
|
-
channel.receiveStream(this.handleReceiveStream.bind(this));
|
|
99
|
-
}
|
|
100
|
-
if (direction === "downstream") {
|
|
101
|
-
this.downstreamPorts.set(clipId, port);
|
|
102
|
-
}
|
|
103
|
-
return { success: true };
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Configure composer
|
|
107
|
-
* According to docs/impl/14-config, only reinitialize when initial=true
|
|
108
|
-
*/
|
|
109
|
-
async handleConfigure(payload) {
|
|
110
|
-
const { config, initial } = payload;
|
|
111
|
-
const hasValidDimensions = config.width > 0 && config.height > 0;
|
|
112
|
-
const hasValidFps = config.fps > 0;
|
|
113
|
-
if (!hasValidDimensions || !hasValidFps) {
|
|
114
|
-
throw new Error(
|
|
115
|
-
`VideoComposeWorker: invalid canvas config width=${config.width}, height=${config.height}, fps=${config.fps}`
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
if (initial) {
|
|
119
|
-
this.channel.state = WorkerState.Ready;
|
|
120
|
-
}
|
|
121
|
-
if (initial || !this.composer) {
|
|
122
|
-
if (this.composer) {
|
|
123
|
-
this.composer.dispose();
|
|
124
|
-
this.composeStream = null;
|
|
125
|
-
}
|
|
126
|
-
this.composer = new VideoComposer(config);
|
|
127
|
-
} else {
|
|
128
|
-
this.composer.updateConfig(config);
|
|
129
|
-
}
|
|
130
|
-
this.channel.notify("configured", {
|
|
131
|
-
config: this.composer.config,
|
|
132
|
-
initialized: initial || false
|
|
133
|
-
});
|
|
134
|
-
return {
|
|
135
|
-
success: true,
|
|
136
|
-
config: this.composer.config
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
async handleReceiveStream(stream, metadata) {
|
|
140
|
-
const { clipId = "default" } = metadata || {};
|
|
141
|
-
if (!this.composer) {
|
|
142
|
-
console.error("[VideoComposeWorker] Composer not configured");
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
const instruction = this.instructionRegistry.get(clipId);
|
|
146
|
-
if (!instruction) {
|
|
147
|
-
console.warn("[VideoComposeWorker] No instructions for clip", clipId);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
const filteredStream = stream.pipeThrough(
|
|
151
|
-
new TransformStream({
|
|
152
|
-
transform: (wrappedFrame, controller) => {
|
|
153
|
-
try {
|
|
154
|
-
const frame = wrappedFrame.frame || wrappedFrame;
|
|
155
|
-
const gopSerial = wrappedFrame.gopSerial;
|
|
156
|
-
const isKeyframe = wrappedFrame.isKeyframe;
|
|
157
|
-
const timestamp = frame.timestamp ?? 0;
|
|
158
|
-
if (this.shouldSkipFrame(clipId, timestamp)) {
|
|
159
|
-
frame.close();
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
const request = this.buildComposeRequest(clipId, instruction, frame, timestamp);
|
|
163
|
-
if (!request) {
|
|
164
|
-
frame.close();
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
request.gopSerial = gopSerial;
|
|
168
|
-
request.isKeyframe = isKeyframe;
|
|
169
|
-
controller.enqueue(request);
|
|
170
|
-
} catch (error) {
|
|
171
|
-
const frame = wrappedFrame.frame || wrappedFrame;
|
|
172
|
-
frame?.close?.();
|
|
173
|
-
throw error;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
})
|
|
177
|
-
);
|
|
178
|
-
const { composeStream, cacheStream } = this.composer.createStreams();
|
|
179
|
-
this.channel.sendStream(cacheStream, metadata);
|
|
180
|
-
filteredStream.pipeTo(composeStream).catch((error) => {
|
|
181
|
-
console.error("[VideoComposeWorker] compose stream error", clipId, error);
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
// private handleGetStream(): ReadableStream<VideoFrame> | undefined {
|
|
185
|
-
// return this.composer?.createStreams()?.cacheStream;
|
|
186
|
-
// }
|
|
187
|
-
/**
|
|
188
|
-
* Flush the composition pipeline
|
|
189
|
-
*/
|
|
190
|
-
async handleFlush() {
|
|
191
|
-
try {
|
|
192
|
-
this.channel.notify("flush_complete", {});
|
|
193
|
-
return { success: true };
|
|
194
|
-
} catch (error) {
|
|
195
|
-
throw {
|
|
196
|
-
code: "FLUSH_ERROR",
|
|
197
|
-
message: error.message
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Get composer statistics
|
|
203
|
-
*/
|
|
204
|
-
async handleGetStats() {
|
|
205
|
-
return {
|
|
206
|
-
configured: this.composer !== null,
|
|
207
|
-
config: this.composer?.config,
|
|
208
|
-
streaming: this.composeStream !== null
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Dispose worker and cleanup resources
|
|
213
|
-
*/
|
|
214
|
-
async handleDispose() {
|
|
215
|
-
if (this.composer) {
|
|
216
|
-
this.composer.dispose();
|
|
217
|
-
this.composer = null;
|
|
218
|
-
}
|
|
219
|
-
this.composeStream = null;
|
|
220
|
-
this.downstreamPorts.get("default")?.close();
|
|
221
|
-
this.upstreamPorts.get("default")?.close();
|
|
222
|
-
this.downstreamPorts.clear();
|
|
223
|
-
this.upstreamPorts.clear();
|
|
224
|
-
this.channel.state = WorkerState.Disposed;
|
|
225
|
-
return { success: true };
|
|
226
|
-
}
|
|
227
|
-
async handleInstallInstructions(data) {
|
|
228
|
-
const { clipId, revision } = data;
|
|
229
|
-
const current = this.instructionRegistry.get(clipId);
|
|
230
|
-
if (current && current.revision > revision) {
|
|
231
|
-
return { success: false };
|
|
232
|
-
}
|
|
233
|
-
this.instructionRegistry.set(clipId, data);
|
|
234
|
-
return { success: true };
|
|
235
|
-
}
|
|
236
|
-
async handleSyncClip(payload) {
|
|
237
|
-
const { clipId, revision, range } = payload;
|
|
238
|
-
const current = this.instructionRegistry.get(clipId);
|
|
239
|
-
if (!current || current.revision > revision) {
|
|
240
|
-
return { success: false };
|
|
241
|
-
}
|
|
242
|
-
this.pendingReplay.set(clipId, { ...range, revision });
|
|
243
|
-
this.channel.notify("sync_ack", { clipId, revision });
|
|
244
|
-
return { success: true };
|
|
245
|
-
}
|
|
246
|
-
async handleDisposeClip(payload) {
|
|
247
|
-
const { clipId } = payload;
|
|
248
|
-
this.instructionRegistry.delete(clipId);
|
|
249
|
-
this.pendingReplay.delete(clipId);
|
|
250
|
-
this.downstreamPorts.get(clipId)?.close();
|
|
251
|
-
this.upstreamPorts.get(clipId)?.close();
|
|
252
|
-
this.downstreamPorts.delete(clipId);
|
|
253
|
-
this.upstreamPorts.delete(clipId);
|
|
254
|
-
return { success: true };
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Check if frame should be skipped (outside dirty range)
|
|
258
|
-
* Returns true if frame is NOT in the dirty range and should use cached version
|
|
259
|
-
*/
|
|
260
|
-
shouldSkipFrame(clipId, timestamp) {
|
|
261
|
-
const dirtyRange = this.pendingReplay.get(clipId);
|
|
262
|
-
if (!dirtyRange) {
|
|
263
|
-
return false;
|
|
264
|
-
}
|
|
265
|
-
if (timestamp >= dirtyRange.startUs && timestamp <= dirtyRange.endUs) {
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
if (timestamp > dirtyRange.endUs) {
|
|
269
|
-
this.pendingReplay.delete(clipId);
|
|
270
|
-
}
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
buildComposeRequest(clipId, instruction, frame, _timestamp) {
|
|
274
|
-
const normalizedTime = this.computeTimelineTimestamp(clipId, frame, instruction.baseConfig);
|
|
275
|
-
const clipStartUs = instruction.baseConfig.timeline?.clipStartUs ?? 0;
|
|
276
|
-
const clipDurationUs = instruction.baseConfig.timeline?.clipDurationUs ?? Infinity;
|
|
277
|
-
const clipEndUs = clipStartUs + clipDurationUs;
|
|
278
|
-
if (normalizedTime < clipStartUs || normalizedTime >= clipEndUs) {
|
|
279
|
-
return null;
|
|
280
|
-
}
|
|
281
|
-
const clipRelativeTime = normalizedTime - clipStartUs;
|
|
282
|
-
const activeLayers = resolveActiveLayers(instruction.layers, clipRelativeTime);
|
|
283
|
-
if (!activeLayers.length) {
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
const layers = activeLayers.map((layer) => materializeLayer(layer, frame));
|
|
287
|
-
return {
|
|
288
|
-
timeUs: normalizedTime,
|
|
289
|
-
layers,
|
|
290
|
-
transition: VideoComposeWorker.buildTransition(
|
|
291
|
-
instruction.transitions,
|
|
292
|
-
clipRelativeTime,
|
|
293
|
-
instruction.baseConfig.timeline
|
|
294
|
-
)
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
static buildTransition(transitions, timeUs, _timeline) {
|
|
298
|
-
const entry = transitions.find((transition) => {
|
|
299
|
-
const { startUs, endUs } = transition.range;
|
|
300
|
-
return timeUs >= startUs && timeUs < endUs;
|
|
301
|
-
});
|
|
302
|
-
if (!entry) {
|
|
303
|
-
return void 0;
|
|
304
|
-
}
|
|
305
|
-
const durationUs = entry.range.endUs - entry.range.startUs;
|
|
306
|
-
const progress = durationUs > 0 ? (timeUs - entry.range.startUs) / durationUs : 0;
|
|
307
|
-
return {
|
|
308
|
-
type: entry.params.type,
|
|
309
|
-
progress: Math.min(Math.max(progress, 0), 1),
|
|
310
|
-
easing: entry.params.easing,
|
|
311
|
-
params: entry.params.payload,
|
|
312
|
-
direction: entry.params.payload?.direction
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
computeTimelineTimestamp(clipId, frame, config) {
|
|
316
|
-
const key = clipId;
|
|
317
|
-
let state = this.streamState.get(key);
|
|
318
|
-
if (!state) {
|
|
319
|
-
state = {
|
|
320
|
-
baseTimestamp: null,
|
|
321
|
-
lastSourceTimestamp: null,
|
|
322
|
-
nextFrameIndex: 0
|
|
323
|
-
};
|
|
324
|
-
this.streamState.set(key, state);
|
|
325
|
-
}
|
|
326
|
-
const timeline = config.timeline;
|
|
327
|
-
if (!timeline) {
|
|
328
|
-
const ts = frame.timestamp ?? 0;
|
|
329
|
-
state.lastSourceTimestamp = frame.timestamp ?? null;
|
|
330
|
-
return ts;
|
|
331
|
-
}
|
|
332
|
-
const { clipStartUs, compositionFps } = timeline;
|
|
333
|
-
const sourceTimestamp = frame.timestamp ?? null;
|
|
334
|
-
if (sourceTimestamp !== null && state.lastSourceTimestamp !== null && sourceTimestamp < state.lastSourceTimestamp) {
|
|
335
|
-
state.baseTimestamp = null;
|
|
336
|
-
state.nextFrameIndex = 0;
|
|
337
|
-
}
|
|
338
|
-
if (state.baseTimestamp === null) {
|
|
339
|
-
state.baseTimestamp = sourceTimestamp ?? 0;
|
|
340
|
-
state.nextFrameIndex = 0;
|
|
341
|
-
if (state.baseTimestamp > 1e3) {
|
|
342
|
-
console.warn(
|
|
343
|
-
`[VideoComposeWorker] First frame timestamp is ${state.baseTimestamp}us, expected ~0. Check MP4Demuxer normalization.`
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
const frameDuration = frameDurationFromFps(compositionFps);
|
|
348
|
-
let frameIndex = state.nextFrameIndex;
|
|
349
|
-
if (sourceTimestamp !== null) {
|
|
350
|
-
const approxIndex = frameIndexFromTimestamp(
|
|
351
|
-
state.baseTimestamp,
|
|
352
|
-
sourceTimestamp,
|
|
353
|
-
compositionFps,
|
|
354
|
-
"nearest"
|
|
355
|
-
);
|
|
356
|
-
frameIndex = Math.max(frameIndex, approxIndex);
|
|
357
|
-
}
|
|
358
|
-
const rawTimeline = clipStartUs + frameIndex * frameDuration;
|
|
359
|
-
const timelineTime = quantizeTimestampToFrame(
|
|
360
|
-
rawTimeline,
|
|
361
|
-
clipStartUs,
|
|
362
|
-
compositionFps,
|
|
363
|
-
"nearest"
|
|
364
|
-
);
|
|
365
|
-
state.nextFrameIndex = frameIndex + 1;
|
|
366
|
-
state.lastSourceTimestamp = sourceTimestamp;
|
|
367
|
-
return timelineTime;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
const worker = new VideoComposeWorker();
|
|
371
|
-
self.addEventListener("beforeunload", () => {
|
|
372
|
-
worker["handleDispose"]();
|
|
373
|
-
});
|
|
374
|
-
const videoCompose_worker = null;
|
|
375
|
-
export {
|
|
376
|
-
VideoComposeWorker,
|
|
377
|
-
videoCompose_worker as default
|
|
378
|
-
};
|
|
379
|
-
//# sourceMappingURL=video-compose.worker.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"video-compose.worker.js","sources":["../../../src/stages/compose/video-compose.worker.ts"],"sourcesContent":["import { WorkerChannel } from '../../worker/WorkerChannel';\nimport { WorkerMessageType, WorkerState } from '../../worker/types';\nimport { VideoComposer } from './VideoComposer';\nimport type { VideoComposeConfig, ComposeRequest, Layer } from './types';\nimport type {\n ClipInstructionSet,\n SerializedLayerPlan,\n SerializedTransitionPlan,\n SerializedImageLayerPayload,\n SerializedTextLayerPayload,\n} from './instructions';\nimport {\n frameDurationFromFps,\n frameIndexFromTimestamp,\n quantizeTimestampToFrame,\n} from '../../utils/time-utils';\nimport type { TimeUs } from '../../model/types';\n\nfunction resolveActiveLayers(\n layers: SerializedLayerPlan[],\n timestamp: number,\n _frame: VideoFrame\n): SerializedLayerPlan[] {\n return layers.filter((layer) => {\n if (layer.status !== 'ready') {\n return false;\n }\n return layer.activeRanges.some(\n (range) => timestamp >= range.startUs && timestamp < range.endUs\n );\n });\n}\n\nfunction materializeLayer(layer: SerializedLayerPlan, frame: VideoFrame): Layer {\n const baseLayer: Layer = {\n id: layer.layerId,\n type: layer.type as any,\n zIndex: layer.zIndex ?? 0,\n visible: true,\n opacity: layer.opacity ?? 1,\n };\n\n if (layer.type === 'video') {\n return {\n ...baseLayer,\n type: 'video',\n videoFrame: frame,\n } as Layer;\n }\n\n if (layer.type === 'text') {\n const payload = layer.payload as SerializedTextLayerPayload;\n return {\n ...baseLayer,\n type: 'text',\n text: payload.text,\n fontFamily: payload.fontFamily,\n fontSize: payload.fontSize,\n fontWeight: payload.fontWeight,\n color: payload.color,\n strokeColor: payload.strokeColor,\n strokeWidth: payload.strokeWidth,\n lineHeight: payload.lineHeight,\n textAlign: payload.align,\n verticalAlign: 'bottom', // Subtitles positioned at bottom\n } as Layer;\n }\n\n if (layer.type === 'image') {\n const payload = layer.payload as SerializedImageLayerPayload;\n const source = payload.bitmapHandle ?? null;\n if (source) {\n return {\n ...baseLayer,\n type: 'image',\n source,\n } as Layer;\n }\n }\n\n return baseLayer;\n}\n\n/**\n * VideoComposeWorker - Visual composition in the pipeline\n * Receives decoded video frames and outputs composed frames\n *\n * Pipeline: DecodeWorker → VideoComposeWorker → EncodeWorker\n *\n * Features:\n * - Multi-layer composition with Canvas2D/WebGL\n * - Transition effects and filters\n * - Text and image overlay support\n * - Stream-based processing with configurable backpressure\n */\nexport class VideoComposeWorker {\n private channel: WorkerChannel;\n private composer: VideoComposer | null = null;\n private composeStream: TransformStream<ComposeRequest, VideoFrame> | null = null;\n\n private downstreamPorts = new Map<string, MessagePort>();\n private upstreamPorts = new Map<string, MessagePort>();\n private instructionRegistry = new Map<string, ClipInstructionSet>();\n private pendingReplay = new Map<string, { startUs: number; endUs: number; revision: number }>();\n private streamState = new Map<string, StreamState>();\n\n constructor() {\n // Initialize WorkerChannel\n this.channel = new WorkerChannel(self as any, {\n name: 'VideoComposeWorker',\n timeout: 30000,\n });\n\n this.setupHandlers();\n }\n\n private setupHandlers(): void {\n // Register message handlers\n this.channel.registerHandler('configure', this.handleConfigure.bind(this));\n this.channel.registerHandler('connect' as any, this.handleConnect.bind(this));\n this.channel.registerHandler('flush', this.handleFlush.bind(this));\n // this.channel.registerHandler('get_stream', this.handleGetStream.bind(this));\n this.channel.registerHandler('get_stats', this.handleGetStats.bind(this));\n this.channel.registerHandler('install_instructions', this.handleInstallInstructions.bind(this));\n this.channel.registerHandler('sync_clip', this.handleSyncClip.bind(this));\n this.channel.registerHandler('dispose_clip', this.handleDisposeClip.bind(this));\n this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));\n }\n\n /**\n * Unified connect handler used by stream pipeline\n */\n private async handleConnect(payload: {\n direction: 'upstream' | 'downstream';\n port: MessagePort;\n streamType: 'video' | 'audio' | 'frame' | 'chunk';\n clipId?: string;\n }): Promise<{ success: boolean }> {\n const { port, direction, clipId = 'default' } = payload;\n if (direction === 'upstream') {\n this.upstreamPorts.set(clipId, port);\n const channel = new WorkerChannel(port, {\n name: 'VideoCompose-Decode',\n timeout: 30000,\n });\n channel.receiveStream(this.handleReceiveStream.bind(this));\n }\n if (direction === 'downstream') {\n this.downstreamPorts.set(clipId, port);\n }\n return { success: true };\n }\n\n /**\n * Configure composer\n * According to docs/impl/14-config, only reinitialize when initial=true\n */\n private async handleConfigure(payload: {\n config: VideoComposeConfig;\n initial?: boolean;\n }): Promise<{\n success: boolean;\n config: VideoComposeConfig;\n }> {\n const { config, initial } = payload;\n\n const hasValidDimensions = config.width > 0 && config.height > 0;\n const hasValidFps = config.fps > 0;\n\n if (!hasValidDimensions || !hasValidFps) {\n throw new Error(\n `VideoComposeWorker: invalid canvas config width=${config.width}, height=${config.height}, fps=${config.fps}`\n );\n }\n\n // Set worker state to ready on initial configuration\n if (initial) {\n this.channel.state = WorkerState.Ready;\n }\n\n if (initial || !this.composer) {\n // Initial configuration or composer doesn't exist\n if (this.composer) {\n this.composer.dispose();\n this.composeStream = null;\n }\n\n // Create new composer\n this.composer = new VideoComposer(config);\n } else {\n // Just update configuration\n this.composer.updateConfig(config);\n }\n\n // Notify configuration complete\n this.channel.notify('configured', {\n config: this.composer.config,\n initialized: initial || false,\n });\n\n return {\n success: true,\n config: this.composer.config,\n };\n }\n\n private async handleReceiveStream(\n stream: ReadableStream,\n metadata?: Record<string, any>\n ): Promise<void> {\n const { clipId = 'default' } = metadata || {};\n\n if (!this.composer) {\n console.error('[VideoComposeWorker] Composer not configured');\n return;\n }\n\n const instruction = this.instructionRegistry.get(clipId);\n if (!instruction) {\n console.warn('[VideoComposeWorker] No instructions for clip', clipId);\n return;\n }\n\n // TODO: ENCODE\n // const downstreamPort = this.downstreamPorts.get(clipId);\n // if (downstreamPort) {\n // const channel = new WorkerChannel(downstreamPort, {\n // name: 'VideoCompose-Encoder',\n // timeout: 30000,\n // });\n // channel.sendStream(encodeStream, {\n // streamType: 'video',\n // ...metadata,\n // });\n // }\n\n const filteredStream = stream.pipeThrough(\n new TransformStream<any, ComposeRequest>({\n transform: (wrappedFrame, controller) => {\n try {\n // Extract frame and metadata from wrapped object\n const frame = wrappedFrame.frame || wrappedFrame;\n const gopSerial = wrappedFrame.gopSerial;\n const isKeyframe = wrappedFrame.isKeyframe;\n\n const timestamp = frame.timestamp ?? 0;\n if (this.shouldSkipFrame(clipId, timestamp)) {\n frame.close();\n return;\n }\n\n const request = this.buildComposeRequest(clipId, instruction, frame, timestamp);\n if (!request) {\n frame.close();\n return;\n }\n (request as any).gopSerial = gopSerial;\n (request as any).isKeyframe = isKeyframe;\n controller.enqueue(request);\n } catch (error) {\n const frame = wrappedFrame.frame || wrappedFrame;\n frame?.close?.();\n throw error;\n }\n },\n })\n );\n\n const { composeStream, cacheStream } = this.composer.createStreams();\n this.channel.sendStream(cacheStream, metadata);\n\n filteredStream.pipeTo(composeStream).catch((error) => {\n console.error('[VideoComposeWorker] compose stream error', clipId, error);\n });\n }\n\n // private handleGetStream(): ReadableStream<VideoFrame> | undefined {\n // return this.composer?.createStreams()?.cacheStream;\n // }\n\n /**\n * Flush the composition pipeline\n */\n private async handleFlush(): Promise<{ success: boolean }> {\n try {\n // Flush any pending frames in the stream\n // The stream will handle this automatically\n\n this.channel.notify('flush_complete', {});\n return { success: true };\n } catch (error: any) {\n throw {\n code: 'FLUSH_ERROR',\n message: error.message,\n };\n }\n }\n\n /**\n * Get composer statistics\n */\n private async handleGetStats(): Promise<{\n configured: boolean;\n config?: VideoComposeConfig;\n streaming: boolean;\n }> {\n return {\n configured: this.composer !== null,\n config: this.composer?.config,\n streaming: this.composeStream !== null,\n };\n }\n\n /**\n * Dispose worker and cleanup resources\n */\n private async handleDispose(): Promise<{ success: boolean }> {\n // Dispose composer\n if (this.composer) {\n this.composer.dispose();\n this.composer = null;\n }\n\n this.composeStream = null;\n\n // Close connections\n this.downstreamPorts.get('default')?.close();\n this.upstreamPorts.get('default')?.close();\n this.downstreamPorts.clear();\n this.upstreamPorts.clear();\n\n this.channel.state = WorkerState.Disposed;\n\n return { success: true };\n }\n\n private async handleInstallInstructions(data: ClipInstructionSet): Promise<{ success: boolean }> {\n const { clipId, revision } = data;\n const current = this.instructionRegistry.get(clipId);\n if (current && current.revision > revision) {\n return { success: false };\n }\n\n this.instructionRegistry.set(clipId, data);\n return { success: true };\n }\n\n private async handleSyncClip(payload: {\n clipId: string;\n revision: number;\n range: { startUs: number; endUs: number };\n }): Promise<{ success: boolean }> {\n const { clipId, revision, range } = payload;\n const current = this.instructionRegistry.get(clipId);\n if (!current || current.revision > revision) {\n return { success: false };\n }\n\n this.pendingReplay.set(clipId, { ...range, revision });\n this.channel.notify('sync_ack', { clipId, revision });\n return { success: true };\n }\n\n private async handleDisposeClip(payload: { clipId: string }): Promise<{ success: boolean }> {\n const { clipId } = payload;\n this.instructionRegistry.delete(clipId);\n this.pendingReplay.delete(clipId);\n this.downstreamPorts.get(clipId)?.close();\n this.upstreamPorts.get(clipId)?.close();\n this.downstreamPorts.delete(clipId);\n this.upstreamPorts.delete(clipId);\n return { success: true };\n }\n\n /**\n * Check if frame should be skipped (outside dirty range)\n * Returns true if frame is NOT in the dirty range and should use cached version\n */\n private shouldSkipFrame(clipId: string, timestamp: number): boolean {\n const dirtyRange = this.pendingReplay.get(clipId);\n if (!dirtyRange) {\n return false; // No dirty range, don't skip (first render or no updates)\n }\n\n // Frame is within dirty range → don't skip, re-compose\n if (timestamp >= dirtyRange.startUs && timestamp <= dirtyRange.endUs) {\n return false;\n }\n\n // Passed dirty range end → clean up\n if (timestamp > dirtyRange.endUs) {\n this.pendingReplay.delete(clipId);\n }\n\n // Frame outside dirty range → skip, use cache\n return true;\n }\n\n private buildComposeRequest(\n clipId: string,\n instruction: ClipInstructionSet,\n frame: VideoFrame,\n _timestamp: number\n ): ComposeRequest | null {\n const normalizedTime = this.computeTimelineTimestamp(clipId, frame, instruction.baseConfig);\n const clipStartUs = instruction.baseConfig.timeline?.clipStartUs ?? 0;\n const clipDurationUs = instruction.baseConfig.timeline?.clipDurationUs ?? Infinity;\n const clipEndUs = clipStartUs + clipDurationUs;\n\n // Check if frame is within clip boundary\n if (normalizedTime < clipStartUs || normalizedTime >= clipEndUs) {\n return null;\n }\n\n const clipRelativeTime = normalizedTime - clipStartUs;\n const activeLayers = resolveActiveLayers(instruction.layers, clipRelativeTime, frame);\n\n if (!activeLayers.length) {\n return null;\n }\n const layers = activeLayers.map((layer) => materializeLayer(layer, frame));\n return {\n timeUs: normalizedTime,\n layers,\n transition: VideoComposeWorker.buildTransition(\n instruction.transitions,\n clipRelativeTime,\n instruction.baseConfig.timeline\n ),\n };\n }\n\n private static buildTransition(\n transitions: SerializedTransitionPlan[],\n timeUs: TimeUs,\n _timeline?: VideoComposeConfig['timeline']\n ) {\n const entry = transitions.find((transition) => {\n const { startUs, endUs } = transition.range;\n return timeUs >= startUs && timeUs < endUs;\n });\n if (!entry) {\n return undefined;\n }\n const durationUs = entry.range.endUs - entry.range.startUs;\n const progress = durationUs > 0 ? (timeUs - entry.range.startUs) / durationUs : 0;\n return {\n type: entry.params.type,\n progress: Math.min(Math.max(progress, 0), 1),\n easing: entry.params.easing,\n params: entry.params.payload,\n direction: entry.params.payload?.direction,\n } as any;\n }\n\n private computeTimelineTimestamp(\n clipId: string,\n frame: VideoFrame,\n config: VideoComposeConfig\n ): TimeUs {\n const key = clipId;\n let state = this.streamState.get(key);\n if (!state) {\n state = {\n baseTimestamp: null,\n lastSourceTimestamp: null,\n nextFrameIndex: 0,\n };\n this.streamState.set(key, state);\n }\n\n const timeline = config.timeline;\n if (!timeline) {\n const ts = frame.timestamp ?? 0;\n state.lastSourceTimestamp = frame.timestamp ?? null;\n return ts;\n }\n\n const { clipStartUs, compositionFps } = timeline;\n const sourceTimestamp = frame.timestamp ?? null;\n\n // Detect stream reset (e.g., after seek)\n if (\n sourceTimestamp !== null &&\n state.lastSourceTimestamp !== null &&\n sourceTimestamp < state.lastSourceTimestamp\n ) {\n state.baseTimestamp = null;\n state.nextFrameIndex = 0;\n }\n\n // Initialize base timestamp (should be 0 or close to 0 after demuxer normalization)\n if (state.baseTimestamp === null) {\n state.baseTimestamp = sourceTimestamp ?? 0;\n state.nextFrameIndex = 0;\n\n // Warn if base is not near zero (indicates demuxer normalization issue)\n if (state.baseTimestamp > 1000) {\n console.warn(\n `[VideoComposeWorker] First frame timestamp is ${state.baseTimestamp}us, expected ~0. ` +\n `Check MP4Demuxer normalization.`\n );\n }\n }\n\n // Since demuxer normalizes to 0, we can directly calculate frame index\n const frameDuration = frameDurationFromFps(compositionFps);\n let frameIndex = state.nextFrameIndex;\n\n if (sourceTimestamp !== null) {\n // Calculate frame index from normalized timestamp\n const approxIndex = frameIndexFromTimestamp(\n state.baseTimestamp,\n sourceTimestamp,\n compositionFps,\n 'nearest'\n );\n frameIndex = Math.max(frameIndex, approxIndex);\n }\n\n // Map to timeline position\n const rawTimeline = clipStartUs + frameIndex * frameDuration;\n const timelineTime = quantizeTimestampToFrame(\n rawTimeline,\n clipStartUs,\n compositionFps,\n 'nearest'\n );\n\n state.nextFrameIndex = frameIndex + 1;\n state.lastSourceTimestamp = sourceTimestamp;\n\n return timelineTime;\n }\n}\n\ninterface StreamState {\n baseTimestamp: number | null;\n lastSourceTimestamp: number | null;\n nextFrameIndex: number;\n}\n\n// Initialize worker\nconst worker = new VideoComposeWorker();\n\n// Handle worker termination\nself.addEventListener('beforeunload', () => {\n worker['handleDispose']();\n});\n\nexport default null; // Required for TypeScript worker compilation\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"video-compose.worker2.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { BaseDecoder } from "./BaseDecoder.js";
|
|
2
|
-
class AudioChunkDecoder extends BaseDecoder {
|
|
3
|
-
// Default values
|
|
4
|
-
static DEFAULT_HIGH_WATER_MARK = 20;
|
|
5
|
-
static DEFAULT_DECODE_QUEUE_THRESHOLD = 16;
|
|
6
|
-
// Exposed properties
|
|
7
|
-
trackId;
|
|
8
|
-
// Backpressure configuration
|
|
9
|
-
highWaterMark;
|
|
10
|
-
decodeQueueThreshold;
|
|
11
|
-
constructor(trackId, config) {
|
|
12
|
-
super(config);
|
|
13
|
-
this.trackId = trackId;
|
|
14
|
-
this.highWaterMark = config?.backpressure?.highWaterMark ?? AudioChunkDecoder.DEFAULT_HIGH_WATER_MARK;
|
|
15
|
-
this.decodeQueueThreshold = AudioChunkDecoder.DEFAULT_DECODE_QUEUE_THRESHOLD;
|
|
16
|
-
}
|
|
17
|
-
// Computed properties
|
|
18
|
-
get isConfigured() {
|
|
19
|
-
return this.isReady;
|
|
20
|
-
}
|
|
21
|
-
get state() {
|
|
22
|
-
return this.decoder?.state || "unconfigured";
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Update configuration - can be called before or after initialization
|
|
26
|
-
*/
|
|
27
|
-
async updateConfig(config) {
|
|
28
|
-
if (!this.isReady && config.codec) {
|
|
29
|
-
await this.configure(config);
|
|
30
|
-
await this.processBufferedChunks();
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
// Implement abstract methods
|
|
35
|
-
async isConfigSupported(config) {
|
|
36
|
-
const result = await AudioDecoder.isConfigSupported({
|
|
37
|
-
codec: config.codec,
|
|
38
|
-
sampleRate: config.sampleRate,
|
|
39
|
-
numberOfChannels: config.numberOfChannels
|
|
40
|
-
});
|
|
41
|
-
return { supported: result.supported ?? false };
|
|
42
|
-
}
|
|
43
|
-
createDecoder(init) {
|
|
44
|
-
return new AudioDecoder(init);
|
|
45
|
-
}
|
|
46
|
-
getDecoderType() {
|
|
47
|
-
return "Audio";
|
|
48
|
-
}
|
|
49
|
-
async configureDecoder(config) {
|
|
50
|
-
if (!this.decoder) return;
|
|
51
|
-
await this.decoder.configure({
|
|
52
|
-
codec: config.codec,
|
|
53
|
-
sampleRate: config.sampleRate,
|
|
54
|
-
numberOfChannels: config.numberOfChannels,
|
|
55
|
-
...config.description && { description: config.description }
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
decode(chunk) {
|
|
59
|
-
this.decoder?.decode(chunk);
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Configure the decoder with codec info (can be called after creation)
|
|
63
|
-
*/
|
|
64
|
-
async configure(config) {
|
|
65
|
-
if (this.isReady) {
|
|
66
|
-
await this.reconfigure(config);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
this.config = config;
|
|
70
|
-
await this.initialize();
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Process any buffered chunks after configuration
|
|
74
|
-
* Note: Audio doesn't buffer in current implementation, but keeping for interface consistency
|
|
75
|
-
*/
|
|
76
|
-
async processBufferedChunks() {
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
export {
|
|
80
|
-
AudioChunkDecoder
|
|
81
|
-
};
|
|
82
|
-
//# sourceMappingURL=AudioChunkDecoder.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AudioChunkDecoder.js","sources":["../../../src/stages/decode/AudioChunkDecoder.ts"],"sourcesContent":["import { AudioDecoderConfig } from './types';\nimport { BaseDecoder } from './BaseDecoder';\n\n/**\n * Audio decoder with streaming support\n * Extends BaseDecoder for common WebCodecs operations\n */\nexport class AudioChunkDecoder extends BaseDecoder<\n AudioDecoder,\n AudioDecoderConfig,\n EncodedAudioChunk,\n AudioData\n> {\n // Default values\n private static readonly DEFAULT_HIGH_WATER_MARK = 20;\n private static readonly DEFAULT_DECODE_QUEUE_THRESHOLD = 16;\n\n // Exposed properties\n readonly trackId: string;\n\n // Backpressure configuration\n protected readonly highWaterMark: number;\n protected readonly decodeQueueThreshold: number;\n\n constructor(trackId: string, config?: Partial<AudioDecoderConfig>) {\n // Initialize with empty config, will be configured later\n super(config as AudioDecoderConfig);\n\n this.trackId = trackId;\n\n // Set backpressure configuration\n this.highWaterMark =\n config?.backpressure?.highWaterMark ?? AudioChunkDecoder.DEFAULT_HIGH_WATER_MARK;\n this.decodeQueueThreshold = AudioChunkDecoder.DEFAULT_DECODE_QUEUE_THRESHOLD;\n }\n\n // Computed properties\n get isConfigured(): boolean {\n return this.isReady;\n }\n\n get state(): string {\n return this.decoder?.state || 'unconfigured';\n }\n\n /**\n * Update configuration - can be called before or after initialization\n */\n async updateConfig(config: Partial<AudioDecoderConfig>): Promise<void> {\n // If decoder is not ready and we have codec info, configure it\n if (!this.isReady && config.codec) {\n await this.configure(config as AudioDecoderConfig);\n await this.processBufferedChunks();\n return;\n }\n\n // Note: AudioDecoder doesn't have many runtime-configurable options\n // Backpressure settings are readonly in this implementation\n // if (config.backpressure) {\n // console.warn('Backpressure settings cannot be changed at runtime');\n // }\n }\n\n // Implement abstract methods\n protected async isConfigSupported(config: AudioDecoderConfig): Promise<{ supported: boolean }> {\n const result = await AudioDecoder.isConfigSupported({\n codec: config.codec,\n sampleRate: config.sampleRate,\n numberOfChannels: config.numberOfChannels,\n });\n return { supported: result.supported ?? false };\n }\n\n protected createDecoder(init: {\n output: (data: AudioData) => void;\n error: (error: DOMException) => void;\n }): AudioDecoder {\n return new AudioDecoder(init);\n }\n\n protected getDecoderType(): string {\n return 'Audio';\n }\n\n protected async configureDecoder(config: AudioDecoderConfig): Promise<void> {\n if (!this.decoder) return;\n\n await this.decoder.configure({\n codec: config.codec,\n sampleRate: config.sampleRate,\n numberOfChannels: config.numberOfChannels,\n ...(config.description && { description: config.description }),\n });\n }\n\n protected decode(chunk: EncodedAudioChunk): void {\n this.decoder?.decode(chunk);\n }\n\n /**\n * Configure the decoder with codec info (can be called after creation)\n */\n async configure(config: AudioDecoderConfig): Promise<void> {\n if (this.isReady) {\n // If already configured, reconfigure\n await this.reconfigure(config);\n return;\n }\n\n this.config = config as any;\n\n // Initialize decoder with new config\n await this.initialize();\n }\n\n /**\n * Process any buffered chunks after configuration\n * Note: Audio doesn't buffer in current implementation, but keeping for interface consistency\n */\n async processBufferedChunks(): Promise<void> {\n // No-op for audio in current implementation\n }\n}\n"],"names":[],"mappings":";AAOO,MAAM,0BAA0B,YAKrC;AAAA;AAAA,EAEA,OAAwB,0BAA0B;AAAA,EAClD,OAAwB,iCAAiC;AAAA;AAAA,EAGhD;AAAA;AAAA,EAGU;AAAA,EACA;AAAA,EAEnB,YAAY,SAAiB,QAAsC;AAEjE,UAAM,MAA4B;AAElC,SAAK,UAAU;AAGf,SAAK,gBACH,QAAQ,cAAc,iBAAiB,kBAAkB;AAC3D,SAAK,uBAAuB,kBAAkB;AAAA,EAChD;AAAA;AAAA,EAGA,IAAI,eAAwB;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,QAAgB;AAClB,WAAO,KAAK,SAAS,SAAS;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,QAAoD;AAErE,QAAI,CAAC,KAAK,WAAW,OAAO,OAAO;AACjC,YAAM,KAAK,UAAU,MAA4B;AACjD,YAAM,KAAK,sBAAA;AACX;AAAA,IACF;AAAA,EAOF;AAAA;AAAA,EAGA,MAAgB,kBAAkB,QAA6D;AAC7F,UAAM,SAAS,MAAM,aAAa,kBAAkB;AAAA,MAClD,OAAO,OAAO;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,kBAAkB,OAAO;AAAA,IAAA,CAC1B;AACD,WAAO,EAAE,WAAW,OAAO,aAAa,MAAA;AAAA,EAC1C;AAAA,EAEU,cAAc,MAGP;AACf,WAAO,IAAI,aAAa,IAAI;AAAA,EAC9B;AAAA,EAEU,iBAAyB;AACjC,WAAO;AAAA,EACT;AAAA,EAEA,MAAgB,iBAAiB,QAA2C;AAC1E,QAAI,CAAC,KAAK,QAAS;AAEnB,UAAM,KAAK,QAAQ,UAAU;AAAA,MAC3B,OAAO,OAAO;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,kBAAkB,OAAO;AAAA,MACzB,GAAI,OAAO,eAAe,EAAE,aAAa,OAAO,YAAA;AAAA,IAAY,CAC7D;AAAA,EACH;AAAA,EAEU,OAAO,OAAgC;AAC/C,SAAK,SAAS,OAAO,KAAK;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,QAA2C;AACzD,QAAI,KAAK,SAAS;AAEhB,YAAM,KAAK,YAAY,MAAM;AAC7B;AAAA,IACF;AAEA,SAAK,SAAS;AAGd,UAAM,KAAK,WAAA;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,wBAAuC;AAAA,EAE7C;AACF;"}
|