@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,483 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
WorkerMessageType2["Ready"] = "ready";
|
|
3
|
-
WorkerMessageType2["Error"] = "error";
|
|
4
|
-
WorkerMessageType2["Dispose"] = "dispose";
|
|
5
|
-
WorkerMessageType2["Configure"] = "configure";
|
|
6
|
-
WorkerMessageType2["LoadResource"] = "load_resource";
|
|
7
|
-
WorkerMessageType2["ResourceLoaded"] = "resource_loaded";
|
|
8
|
-
WorkerMessageType2["ResourceProgress"] = "resource_progress";
|
|
9
|
-
WorkerMessageType2["ConfigureDemux"] = "configure_demux";
|
|
10
|
-
WorkerMessageType2["AppendBuffer"] = "append_buffer";
|
|
11
|
-
WorkerMessageType2["DemuxSamples"] = "demux_samples";
|
|
12
|
-
WorkerMessageType2["FlushDemux"] = "flush_demux";
|
|
13
|
-
WorkerMessageType2["ConfigureDecode"] = "configure_decode";
|
|
14
|
-
WorkerMessageType2["DecodeChunk"] = "decode_chunk";
|
|
15
|
-
WorkerMessageType2["DecodedFrame"] = "decoded_frame";
|
|
16
|
-
WorkerMessageType2["SeekGop"] = "seek_gop";
|
|
17
|
-
WorkerMessageType2["SetComposition"] = "set_composition";
|
|
18
|
-
WorkerMessageType2["ApplyPatch"] = "apply_patch";
|
|
19
|
-
WorkerMessageType2["RenderFrame"] = "render_frame";
|
|
20
|
-
WorkerMessageType2["ComposeFrameReady"] = "compose_frame_ready";
|
|
21
|
-
WorkerMessageType2["ConfigureEncode"] = "configure_encode";
|
|
22
|
-
WorkerMessageType2["EncodeFrame"] = "encode_frame";
|
|
23
|
-
WorkerMessageType2["EncodeAudio"] = "encode_audio";
|
|
24
|
-
WorkerMessageType2["EncodedChunk"] = "encoded_chunk";
|
|
25
|
-
WorkerMessageType2["FlushEncode"] = "flush_encode";
|
|
26
|
-
WorkerMessageType2["ConfigureMux"] = "configure_mux";
|
|
27
|
-
WorkerMessageType2["AddChunk"] = "add_chunk";
|
|
28
|
-
WorkerMessageType2["FinishMux"] = "finish_mux";
|
|
29
|
-
WorkerMessageType2["MuxComplete"] = "mux_complete";
|
|
30
|
-
WorkerMessageType2["PerformanceStats"] = "performance_stats";
|
|
31
|
-
WorkerMessageType2["RenderWindow"] = "renderWindow";
|
|
32
|
-
WorkerMessageType2["AudioTrackAdd"] = "audio_track:add";
|
|
33
|
-
WorkerMessageType2["AudioTrackRemove"] = "audio_track:remove";
|
|
34
|
-
WorkerMessageType2["AudioTrackUpdate"] = "audio_track:update";
|
|
35
|
-
return WorkerMessageType2;
|
|
36
|
-
})(WorkerMessageType || {});
|
|
37
|
-
var WorkerState = /* @__PURE__ */ ((WorkerState2) => {
|
|
38
|
-
WorkerState2["Idle"] = "idle";
|
|
39
|
-
WorkerState2["Initializing"] = "initializing";
|
|
40
|
-
WorkerState2["Ready"] = "ready";
|
|
41
|
-
WorkerState2["Processing"] = "processing";
|
|
42
|
-
WorkerState2["Error"] = "error";
|
|
43
|
-
WorkerState2["Disposed"] = "disposed";
|
|
44
|
-
return WorkerState2;
|
|
45
|
-
})(WorkerState || {});
|
|
46
|
-
const defaultRetryConfig = {
|
|
47
|
-
maxRetries: 3,
|
|
48
|
-
initialDelay: 100,
|
|
49
|
-
maxDelay: 5e3,
|
|
50
|
-
backoffFactor: 2,
|
|
51
|
-
retryableErrors: ["TIMEOUT", "NETWORK_ERROR", "WORKER_BUSY"]
|
|
52
|
-
};
|
|
53
|
-
function calculateRetryDelay(attempt, config) {
|
|
54
|
-
const { initialDelay = 100, maxDelay = 5e3, backoffFactor = 2 } = config;
|
|
55
|
-
const delay = initialDelay * Math.pow(backoffFactor, attempt - 1);
|
|
56
|
-
return Math.min(delay, maxDelay);
|
|
57
|
-
}
|
|
58
|
-
function isRetryableError(error, config) {
|
|
59
|
-
const { retryableErrors = defaultRetryConfig.retryableErrors } = config;
|
|
60
|
-
if (!error) return false;
|
|
61
|
-
const errorCode = error.code || error.name;
|
|
62
|
-
if (errorCode && retryableErrors.includes(errorCode)) {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
const message = error.message || "";
|
|
66
|
-
if (message.includes("timeout") || message.includes("Timeout")) {
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
async function withRetry(fn, config) {
|
|
72
|
-
const { maxRetries } = config;
|
|
73
|
-
let lastError;
|
|
74
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
75
|
-
try {
|
|
76
|
-
return await fn();
|
|
77
|
-
} catch (error) {
|
|
78
|
-
lastError = error;
|
|
79
|
-
if (!isRetryableError(error, config)) {
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
if (attempt === maxRetries) {
|
|
83
|
-
throw error;
|
|
84
|
-
}
|
|
85
|
-
const delay = calculateRetryDelay(attempt, config);
|
|
86
|
-
await sleep(delay);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
throw lastError || new Error("Retry failed");
|
|
90
|
-
}
|
|
91
|
-
function sleep(ms) {
|
|
92
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
93
|
-
}
|
|
94
|
-
function isTransferable(obj) {
|
|
95
|
-
return obj instanceof ArrayBuffer || obj instanceof MessagePort || typeof ImageBitmap !== "undefined" && obj instanceof ImageBitmap || typeof OffscreenCanvas !== "undefined" && obj instanceof OffscreenCanvas || typeof ReadableStream !== "undefined" && obj instanceof ReadableStream || typeof WritableStream !== "undefined" && obj instanceof WritableStream || typeof TransformStream !== "undefined" && obj instanceof TransformStream;
|
|
96
|
-
}
|
|
97
|
-
function findTransferables(obj, transferables) {
|
|
98
|
-
if (!obj || typeof obj !== "object") {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (isTransferable(obj)) {
|
|
102
|
-
transferables.push(obj);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
if (obj instanceof VideoFrame) {
|
|
106
|
-
transferables.push(obj);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (typeof AudioData !== "undefined" && obj instanceof AudioData) {
|
|
110
|
-
transferables.push(obj);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (typeof EncodedVideoChunk !== "undefined" && obj instanceof EncodedVideoChunk || typeof EncodedAudioChunk !== "undefined" && obj instanceof EncodedAudioChunk) {
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
if (Array.isArray(obj)) {
|
|
117
|
-
for (const item of obj) {
|
|
118
|
-
findTransferables(item, transferables);
|
|
119
|
-
}
|
|
120
|
-
} else {
|
|
121
|
-
for (const key in obj) {
|
|
122
|
-
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
123
|
-
findTransferables(obj[key], transferables);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
function extractTransferables(payload) {
|
|
129
|
-
const transferables = [];
|
|
130
|
-
findTransferables(payload, transferables);
|
|
131
|
-
return transferables;
|
|
132
|
-
}
|
|
133
|
-
class WorkerChannel {
|
|
134
|
-
name;
|
|
135
|
-
port;
|
|
136
|
-
pendingRequests = /* @__PURE__ */ new Map();
|
|
137
|
-
messageHandlers = {};
|
|
138
|
-
state = WorkerState.Idle;
|
|
139
|
-
defaultTimeout;
|
|
140
|
-
defaultMaxRetries;
|
|
141
|
-
constructor(port, config) {
|
|
142
|
-
this.name = config.name;
|
|
143
|
-
this.port = port;
|
|
144
|
-
this.defaultTimeout = config.timeout ?? 3e4;
|
|
145
|
-
this.defaultMaxRetries = config.maxRetries ?? 3;
|
|
146
|
-
this.setupMessageHandler();
|
|
147
|
-
this.state = WorkerState.Ready;
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Send a message and wait for response with retry support
|
|
151
|
-
*/
|
|
152
|
-
async send(type, payload, options) {
|
|
153
|
-
const maxRetries = options?.maxRetries ?? this.defaultMaxRetries;
|
|
154
|
-
const retryConfig = {
|
|
155
|
-
...defaultRetryConfig,
|
|
156
|
-
maxRetries,
|
|
157
|
-
...options?.retryConfig
|
|
158
|
-
};
|
|
159
|
-
return withRetry(() => this.sendOnce(type, payload, options), retryConfig);
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Send a message once (without retry)
|
|
163
|
-
*/
|
|
164
|
-
async sendOnce(type, payload, options) {
|
|
165
|
-
const id = this.generateMessageId();
|
|
166
|
-
const timeout = options?.timeout ?? this.defaultTimeout;
|
|
167
|
-
const message = {
|
|
168
|
-
type,
|
|
169
|
-
id,
|
|
170
|
-
payload,
|
|
171
|
-
timestamp: Date.now()
|
|
172
|
-
};
|
|
173
|
-
return new Promise((resolve, reject) => {
|
|
174
|
-
const request = {
|
|
175
|
-
id,
|
|
176
|
-
type,
|
|
177
|
-
timestamp: Date.now(),
|
|
178
|
-
timeout,
|
|
179
|
-
resolve,
|
|
180
|
-
reject
|
|
181
|
-
};
|
|
182
|
-
this.pendingRequests.set(id, request);
|
|
183
|
-
const timeoutId = setTimeout(() => {
|
|
184
|
-
const pending = this.pendingRequests.get(id);
|
|
185
|
-
if (pending) {
|
|
186
|
-
this.pendingRequests.delete(id);
|
|
187
|
-
const error = new Error(`Request timeout: ${id} ${type} (${timeout}ms)`);
|
|
188
|
-
error.code = "TIMEOUT";
|
|
189
|
-
pending.reject(error);
|
|
190
|
-
}
|
|
191
|
-
}, timeout);
|
|
192
|
-
request.timeoutId = timeoutId;
|
|
193
|
-
if (options?.transfer) {
|
|
194
|
-
this.port.postMessage(message, options.transfer);
|
|
195
|
-
} else {
|
|
196
|
-
this.port.postMessage(message);
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Send a message without waiting for response
|
|
202
|
-
*/
|
|
203
|
-
post(type, payload, transfer) {
|
|
204
|
-
const message = {
|
|
205
|
-
type,
|
|
206
|
-
id: this.generateMessageId(),
|
|
207
|
-
payload,
|
|
208
|
-
timestamp: Date.now()
|
|
209
|
-
};
|
|
210
|
-
if (transfer) {
|
|
211
|
-
this.port.postMessage(message, transfer);
|
|
212
|
-
} else {
|
|
213
|
-
this.port.postMessage(message);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Register a message handler
|
|
218
|
-
*/
|
|
219
|
-
on(type, handler) {
|
|
220
|
-
this.messageHandlers[type] = handler;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Unregister a message handler
|
|
224
|
-
*/
|
|
225
|
-
off(type) {
|
|
226
|
-
delete this.messageHandlers[type];
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Dispose the channel
|
|
230
|
-
*/
|
|
231
|
-
dispose() {
|
|
232
|
-
this.state = WorkerState.Disposed;
|
|
233
|
-
for (const [, request] of this.pendingRequests) {
|
|
234
|
-
if (request.timeoutId) {
|
|
235
|
-
clearTimeout(request.timeoutId);
|
|
236
|
-
}
|
|
237
|
-
request.reject(new Error("Channel disposed"));
|
|
238
|
-
}
|
|
239
|
-
this.pendingRequests.clear();
|
|
240
|
-
this.port.onmessage = null;
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Setup message handler for incoming messages
|
|
244
|
-
*/
|
|
245
|
-
setupMessageHandler() {
|
|
246
|
-
this.port.onmessage = async (event) => {
|
|
247
|
-
const data = event.data;
|
|
248
|
-
if (this.isResponse(data)) {
|
|
249
|
-
this.handleResponse(data);
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
if (this.isRequest(data)) {
|
|
253
|
-
await this.handleRequest(data);
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Handle incoming request
|
|
260
|
-
*/
|
|
261
|
-
async handleRequest(message) {
|
|
262
|
-
const handler = this.messageHandlers[message.type];
|
|
263
|
-
if (!handler) {
|
|
264
|
-
this.sendResponse(message.id, false, null, {
|
|
265
|
-
code: "NO_HANDLER",
|
|
266
|
-
message: `No handler registered for message type: ${message.type}`
|
|
267
|
-
});
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
this.state = WorkerState.Processing;
|
|
271
|
-
Promise.resolve().then(() => handler(message.payload, message.transfer)).then((result) => {
|
|
272
|
-
this.sendResponse(message.id, true, result);
|
|
273
|
-
this.state = WorkerState.Ready;
|
|
274
|
-
}).catch((error) => {
|
|
275
|
-
const workerError = {
|
|
276
|
-
code: "HANDLER_ERROR",
|
|
277
|
-
message: error instanceof Error ? error.message : String(error),
|
|
278
|
-
stack: error instanceof Error ? error.stack : void 0
|
|
279
|
-
};
|
|
280
|
-
this.sendResponse(message.id, false, null, workerError);
|
|
281
|
-
this.state = WorkerState.Ready;
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Handle incoming response
|
|
286
|
-
*/
|
|
287
|
-
handleResponse(response) {
|
|
288
|
-
const request = this.pendingRequests.get(response.id);
|
|
289
|
-
if (!request) {
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
this.pendingRequests.delete(response.id);
|
|
293
|
-
if (request.timeoutId) {
|
|
294
|
-
clearTimeout(request.timeoutId);
|
|
295
|
-
}
|
|
296
|
-
if (response.success) {
|
|
297
|
-
request.resolve(response.result);
|
|
298
|
-
} else {
|
|
299
|
-
const error = new Error(response.error?.message || "Unknown error");
|
|
300
|
-
if (response.error) {
|
|
301
|
-
Object.assign(error, response.error);
|
|
302
|
-
}
|
|
303
|
-
request.reject(error);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* Send a response message
|
|
308
|
-
*/
|
|
309
|
-
sendResponse(id, success, result, error) {
|
|
310
|
-
let transfer = [];
|
|
311
|
-
if (isTransferable(result)) {
|
|
312
|
-
transfer.push(result);
|
|
313
|
-
}
|
|
314
|
-
const response = {
|
|
315
|
-
id,
|
|
316
|
-
success,
|
|
317
|
-
result,
|
|
318
|
-
error,
|
|
319
|
-
timestamp: Date.now()
|
|
320
|
-
};
|
|
321
|
-
this.port.postMessage(response, transfer);
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Check if message is a response
|
|
325
|
-
*/
|
|
326
|
-
isResponse(data) {
|
|
327
|
-
return data && typeof data === "object" && "id" in data && "success" in data && !("type" in data);
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Check if message is a request
|
|
331
|
-
*/
|
|
332
|
-
isRequest(data) {
|
|
333
|
-
return data && typeof data === "object" && "id" in data && "type" in data;
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* Generate unique message ID
|
|
337
|
-
*/
|
|
338
|
-
generateMessageId() {
|
|
339
|
-
return `${this.name}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
340
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* Send a notification message without waiting for response
|
|
343
|
-
* Alias for post() method for compatibility
|
|
344
|
-
*/
|
|
345
|
-
notify(type, payload, transfer) {
|
|
346
|
-
this.post(type, payload, transfer);
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* Register a message handler
|
|
350
|
-
* Alias for on() method for compatibility
|
|
351
|
-
*/
|
|
352
|
-
registerHandler(type, handler) {
|
|
353
|
-
this.on(type, handler);
|
|
354
|
-
}
|
|
355
|
-
/**
|
|
356
|
-
* Send a ReadableStream to another worker
|
|
357
|
-
* Automatically handles transferable streams vs chunk-by-chunk fallback
|
|
358
|
-
*/
|
|
359
|
-
async sendStream(stream, metadata) {
|
|
360
|
-
const streamId = metadata?.streamId || this.generateMessageId();
|
|
361
|
-
if (isTransferable(stream)) {
|
|
362
|
-
this.port.postMessage(
|
|
363
|
-
{
|
|
364
|
-
type: "stream_transfer",
|
|
365
|
-
...metadata,
|
|
366
|
-
stream,
|
|
367
|
-
streamId
|
|
368
|
-
},
|
|
369
|
-
[stream]
|
|
370
|
-
// Transfer ownership
|
|
371
|
-
);
|
|
372
|
-
} else {
|
|
373
|
-
await this.streamChunks(stream, streamId, metadata);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
/**
|
|
377
|
-
* Stream chunks from a ReadableStream (fallback when transfer is not supported)
|
|
378
|
-
*/
|
|
379
|
-
async streamChunks(stream, streamId, metadata) {
|
|
380
|
-
const reader = stream.getReader();
|
|
381
|
-
this.post("stream_start", {
|
|
382
|
-
streamId,
|
|
383
|
-
...metadata,
|
|
384
|
-
mode: "chunk_transfer"
|
|
385
|
-
});
|
|
386
|
-
try {
|
|
387
|
-
while (true) {
|
|
388
|
-
const { done, value } = await reader.read();
|
|
389
|
-
if (done) {
|
|
390
|
-
this.post("stream_end", {
|
|
391
|
-
streamId,
|
|
392
|
-
...metadata
|
|
393
|
-
});
|
|
394
|
-
break;
|
|
395
|
-
}
|
|
396
|
-
const transfer = [];
|
|
397
|
-
if (value instanceof ArrayBuffer) {
|
|
398
|
-
transfer.push(value);
|
|
399
|
-
} else if (value instanceof Uint8Array) {
|
|
400
|
-
transfer.push(value.buffer);
|
|
401
|
-
} else if (typeof AudioData !== "undefined" && value instanceof AudioData) {
|
|
402
|
-
transfer.push(value);
|
|
403
|
-
} else if (typeof VideoFrame !== "undefined" && value instanceof VideoFrame) {
|
|
404
|
-
transfer.push(value);
|
|
405
|
-
} else if (typeof value === "object" && value !== null) {
|
|
406
|
-
const extracted = extractTransferables(value);
|
|
407
|
-
transfer.push(...extracted);
|
|
408
|
-
}
|
|
409
|
-
this.post(
|
|
410
|
-
"stream_chunk",
|
|
411
|
-
{
|
|
412
|
-
streamId,
|
|
413
|
-
chunk: value,
|
|
414
|
-
...metadata
|
|
415
|
-
},
|
|
416
|
-
transfer
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
} catch (error) {
|
|
420
|
-
this.post("stream_error", {
|
|
421
|
-
streamId,
|
|
422
|
-
error: error instanceof Error ? error.message : String(error),
|
|
423
|
-
...metadata
|
|
424
|
-
});
|
|
425
|
-
throw error;
|
|
426
|
-
} finally {
|
|
427
|
-
reader.releaseLock();
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* Receive a stream from another worker
|
|
432
|
-
* Handles both transferable streams and chunk-by-chunk reconstruction
|
|
433
|
-
*/
|
|
434
|
-
async receiveStream(onStream) {
|
|
435
|
-
const chunkedStreams = /* @__PURE__ */ new Map();
|
|
436
|
-
const prev = this.port.onmessage;
|
|
437
|
-
const handler = (event) => {
|
|
438
|
-
const raw = event.data;
|
|
439
|
-
const envelopeType = raw?.type;
|
|
440
|
-
const hasPayload = raw && typeof raw === "object" && "payload" in raw;
|
|
441
|
-
const payload = hasPayload ? raw.payload : raw;
|
|
442
|
-
if (envelopeType === "stream_transfer" && payload?.stream) {
|
|
443
|
-
onStream(payload.stream, payload);
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
if (envelopeType === "stream_start" && payload?.streamId) {
|
|
447
|
-
const stream = new ReadableStream({
|
|
448
|
-
start(controller) {
|
|
449
|
-
chunkedStreams.set(payload.streamId, { controller, metadata: payload });
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
onStream(stream, payload);
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
if (envelopeType === "stream_chunk" && payload?.streamId && chunkedStreams.has(payload.streamId)) {
|
|
456
|
-
const s = chunkedStreams.get(payload.streamId);
|
|
457
|
-
if (s) s.controller.enqueue(payload.chunk);
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
if (envelopeType === "stream_end" && payload?.streamId && chunkedStreams.has(payload.streamId)) {
|
|
461
|
-
const s = chunkedStreams.get(payload.streamId);
|
|
462
|
-
if (s) {
|
|
463
|
-
s.controller.close();
|
|
464
|
-
chunkedStreams.delete(payload.streamId);
|
|
465
|
-
}
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
if (envelopeType === "stream_error" && payload?.streamId && chunkedStreams.has(payload.streamId)) {
|
|
469
|
-
const s = chunkedStreams.get(payload.streamId);
|
|
470
|
-
if (s) {
|
|
471
|
-
s.controller.error(new Error(String(payload.error || "stream error")));
|
|
472
|
-
chunkedStreams.delete(payload.streamId);
|
|
473
|
-
}
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
if (typeof prev === "function") prev.call(this.port, event);
|
|
477
|
-
};
|
|
478
|
-
this.port.onmessage = handler;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
1
|
+
import { W as WorkerChannel, a as WorkerMessageType, b as WorkerState } from "../../WorkerChannel.js";
|
|
481
2
|
const MICROSECONDS_PER_SECOND = 1e6;
|
|
482
3
|
const DEFAULT_FPS = 30;
|
|
483
4
|
function normalizeFps(value) {
|
|
@@ -1311,8 +832,11 @@ class VideoComposer {
|
|
|
1311
832
|
this.filterProcessor.clearCache();
|
|
1312
833
|
}
|
|
1313
834
|
}
|
|
1314
|
-
function resolveActiveLayers(layers, timestamp
|
|
835
|
+
function resolveActiveLayers(layers, timestamp) {
|
|
1315
836
|
return layers.filter((layer) => {
|
|
837
|
+
if (layer.layerId.includes("base-video")) {
|
|
838
|
+
return true;
|
|
839
|
+
}
|
|
1316
840
|
if (layer.status !== "ready") {
|
|
1317
841
|
return false;
|
|
1318
842
|
}
|
|
@@ -1371,11 +895,12 @@ class VideoComposeWorker {
|
|
|
1371
895
|
channel;
|
|
1372
896
|
composer = null;
|
|
1373
897
|
composeStream = null;
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
streamState =
|
|
898
|
+
clipId = null;
|
|
899
|
+
downstreamPort = null;
|
|
900
|
+
upstreamPort = null;
|
|
901
|
+
instructions = null;
|
|
902
|
+
streamState = null;
|
|
903
|
+
imageBitmap = null;
|
|
1379
904
|
constructor() {
|
|
1380
905
|
this.channel = new WorkerChannel(self, {
|
|
1381
906
|
name: "VideoComposeWorker",
|
|
@@ -1389,7 +914,7 @@ class VideoComposeWorker {
|
|
|
1389
914
|
this.channel.registerHandler("flush", this.handleFlush.bind(this));
|
|
1390
915
|
this.channel.registerHandler("get_stats", this.handleGetStats.bind(this));
|
|
1391
916
|
this.channel.registerHandler("install_instructions", this.handleInstallInstructions.bind(this));
|
|
1392
|
-
this.channel.registerHandler("
|
|
917
|
+
this.channel.registerHandler("receive_image", this.handleReceiveImage.bind(this));
|
|
1393
918
|
this.channel.registerHandler("dispose_clip", this.handleDisposeClip.bind(this));
|
|
1394
919
|
this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));
|
|
1395
920
|
}
|
|
@@ -1397,9 +922,12 @@ class VideoComposeWorker {
|
|
|
1397
922
|
* Unified connect handler used by stream pipeline
|
|
1398
923
|
*/
|
|
1399
924
|
async handleConnect(payload) {
|
|
1400
|
-
const { port, direction, clipId
|
|
925
|
+
const { port, direction, clipId } = payload;
|
|
926
|
+
if (clipId && !this.clipId) {
|
|
927
|
+
this.clipId = clipId;
|
|
928
|
+
}
|
|
1401
929
|
if (direction === "upstream") {
|
|
1402
|
-
this.
|
|
930
|
+
this.upstreamPort = port;
|
|
1403
931
|
const channel = new WorkerChannel(port, {
|
|
1404
932
|
name: "VideoCompose-Decode",
|
|
1405
933
|
timeout: 3e4
|
|
@@ -1407,7 +935,7 @@ class VideoComposeWorker {
|
|
|
1407
935
|
channel.receiveStream(this.handleReceiveStream.bind(this));
|
|
1408
936
|
}
|
|
1409
937
|
if (direction === "downstream") {
|
|
1410
|
-
this.
|
|
938
|
+
this.downstreamPort = port;
|
|
1411
939
|
}
|
|
1412
940
|
return { success: true };
|
|
1413
941
|
}
|
|
@@ -1446,14 +974,12 @@ class VideoComposeWorker {
|
|
|
1446
974
|
};
|
|
1447
975
|
}
|
|
1448
976
|
async handleReceiveStream(stream, metadata) {
|
|
1449
|
-
const { clipId = "default" } = metadata || {};
|
|
1450
977
|
if (!this.composer) {
|
|
1451
978
|
console.error("[VideoComposeWorker] Composer not configured");
|
|
1452
979
|
return;
|
|
1453
980
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
console.warn("[VideoComposeWorker] No instructions for clip", clipId);
|
|
981
|
+
if (!this.instructions) {
|
|
982
|
+
console.warn("[VideoComposeWorker] No instructions installed");
|
|
1457
983
|
return;
|
|
1458
984
|
}
|
|
1459
985
|
const filteredStream = stream.pipeThrough(
|
|
@@ -1463,12 +989,7 @@ class VideoComposeWorker {
|
|
|
1463
989
|
const frame = wrappedFrame.frame || wrappedFrame;
|
|
1464
990
|
const gopSerial = wrappedFrame.gopSerial;
|
|
1465
991
|
const isKeyframe = wrappedFrame.isKeyframe;
|
|
1466
|
-
const
|
|
1467
|
-
if (this.shouldSkipFrame(clipId, timestamp)) {
|
|
1468
|
-
frame.close();
|
|
1469
|
-
return;
|
|
1470
|
-
}
|
|
1471
|
-
const request = this.buildComposeRequest(clipId, instruction, frame, timestamp);
|
|
992
|
+
const request = this.buildComposeRequest(this.instructions, frame);
|
|
1472
993
|
if (!request) {
|
|
1473
994
|
frame.close();
|
|
1474
995
|
return;
|
|
@@ -1487,7 +1008,7 @@ class VideoComposeWorker {
|
|
|
1487
1008
|
const { composeStream, cacheStream } = this.composer.createStreams();
|
|
1488
1009
|
this.channel.sendStream(cacheStream, metadata);
|
|
1489
1010
|
filteredStream.pipeTo(composeStream).catch((error) => {
|
|
1490
|
-
console.error("[VideoComposeWorker] compose stream error", clipId, error);
|
|
1011
|
+
console.error("[VideoComposeWorker] compose stream error", this.clipId, error);
|
|
1491
1012
|
});
|
|
1492
1013
|
}
|
|
1493
1014
|
// private handleGetStream(): ReadableStream<VideoFrame> | undefined {
|
|
@@ -1526,61 +1047,96 @@ class VideoComposeWorker {
|
|
|
1526
1047
|
this.composer = null;
|
|
1527
1048
|
}
|
|
1528
1049
|
this.composeStream = null;
|
|
1529
|
-
this.
|
|
1530
|
-
this.
|
|
1531
|
-
this.
|
|
1532
|
-
this.
|
|
1050
|
+
this.downstreamPort?.close();
|
|
1051
|
+
this.upstreamPort?.close();
|
|
1052
|
+
this.downstreamPort = null;
|
|
1053
|
+
this.upstreamPort = null;
|
|
1054
|
+
this.imageBitmap?.close();
|
|
1055
|
+
this.imageBitmap = null;
|
|
1056
|
+
this.instructions = null;
|
|
1057
|
+
this.streamState = null;
|
|
1533
1058
|
this.channel.state = WorkerState.Disposed;
|
|
1534
1059
|
return { success: true };
|
|
1535
1060
|
}
|
|
1536
|
-
async handleInstallInstructions(
|
|
1537
|
-
const { clipId, revision } =
|
|
1538
|
-
|
|
1539
|
-
|
|
1061
|
+
async handleInstallInstructions(payload) {
|
|
1062
|
+
const { clipId, revision } = payload;
|
|
1063
|
+
if (!this.clipId) {
|
|
1064
|
+
this.clipId = clipId;
|
|
1065
|
+
}
|
|
1066
|
+
if (this.instructions && this.instructions.revision > revision) {
|
|
1540
1067
|
return { success: false };
|
|
1541
1068
|
}
|
|
1542
|
-
this.
|
|
1069
|
+
this.instructions = payload;
|
|
1543
1070
|
return { success: true };
|
|
1544
1071
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1072
|
+
/**
|
|
1073
|
+
* Receive image data from ResourceLoader
|
|
1074
|
+
* Note: ImageBitmap is required because VideoFrame constructor in Worker context
|
|
1075
|
+
* only accepts ImageBitmap/OffscreenCanvas, not HTMLImageElement or Blob
|
|
1076
|
+
*/
|
|
1077
|
+
async handleReceiveImage(payload) {
|
|
1078
|
+
const { clipId, imageBitmap } = payload;
|
|
1079
|
+
if (!this.clipId) {
|
|
1080
|
+
this.clipId = clipId;
|
|
1081
|
+
}
|
|
1082
|
+
if (this.imageBitmap) {
|
|
1083
|
+
this.imageBitmap.close();
|
|
1084
|
+
}
|
|
1085
|
+
this.imageBitmap = imageBitmap;
|
|
1086
|
+
if (this.instructions) {
|
|
1087
|
+
await this.startImageFrameStream();
|
|
1550
1088
|
}
|
|
1551
|
-
this.pendingReplay.set(clipId, { ...range, revision });
|
|
1552
|
-
this.channel.notify("sync_ack", { clipId, revision });
|
|
1553
1089
|
return { success: true };
|
|
1554
1090
|
}
|
|
1555
|
-
async handleDisposeClip(
|
|
1556
|
-
|
|
1557
|
-
this.
|
|
1558
|
-
this.
|
|
1559
|
-
this.
|
|
1560
|
-
this.
|
|
1561
|
-
this.
|
|
1562
|
-
this.
|
|
1091
|
+
async handleDisposeClip() {
|
|
1092
|
+
this.instructions = null;
|
|
1093
|
+
this.streamState = null;
|
|
1094
|
+
this.downstreamPort?.close();
|
|
1095
|
+
this.upstreamPort?.close();
|
|
1096
|
+
this.downstreamPort = null;
|
|
1097
|
+
this.upstreamPort = null;
|
|
1098
|
+
this.imageBitmap?.close();
|
|
1099
|
+
this.imageBitmap = null;
|
|
1563
1100
|
return { success: true };
|
|
1564
1101
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
*/
|
|
1569
|
-
shouldSkipFrame(clipId, timestamp) {
|
|
1570
|
-
const dirtyRange = this.pendingReplay.get(clipId);
|
|
1571
|
-
if (!dirtyRange) {
|
|
1572
|
-
return false;
|
|
1573
|
-
}
|
|
1574
|
-
if (timestamp >= dirtyRange.startUs && timestamp <= dirtyRange.endUs) {
|
|
1575
|
-
return false;
|
|
1102
|
+
async startImageFrameStream() {
|
|
1103
|
+
if (!this.instructions || !this.composer) {
|
|
1104
|
+
return;
|
|
1576
1105
|
}
|
|
1577
|
-
|
|
1578
|
-
|
|
1106
|
+
const timeline = this.instructions.baseConfig.timeline;
|
|
1107
|
+
if (!timeline) {
|
|
1108
|
+
return;
|
|
1579
1109
|
}
|
|
1580
|
-
|
|
1110
|
+
const { composeStream, cacheStream } = this.composer.createStreams();
|
|
1111
|
+
const { clipDurationUs, compositionFps } = timeline;
|
|
1112
|
+
let currentTimeUs = 0;
|
|
1113
|
+
const readableStream = new ReadableStream({
|
|
1114
|
+
pull: (controller) => {
|
|
1115
|
+
if (currentTimeUs >= clipDurationUs) {
|
|
1116
|
+
controller.close();
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
const videoFrame = new VideoFrame(this.imageBitmap, {
|
|
1120
|
+
timestamp: currentTimeUs,
|
|
1121
|
+
duration: frameDurationFromFps(compositionFps)
|
|
1122
|
+
});
|
|
1123
|
+
const request = this.buildComposeRequest(this.instructions, videoFrame);
|
|
1124
|
+
if (request) {
|
|
1125
|
+
controller.enqueue(request);
|
|
1126
|
+
}
|
|
1127
|
+
currentTimeUs += frameDurationFromFps(compositionFps);
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
this.channel.sendStream(cacheStream, {
|
|
1131
|
+
streamType: "video",
|
|
1132
|
+
clipId: this.clipId
|
|
1133
|
+
});
|
|
1134
|
+
readableStream.pipeTo(composeStream).catch((error) => {
|
|
1135
|
+
console.error("[VideoComposeWorker] image frame stream error", this.clipId, error);
|
|
1136
|
+
});
|
|
1581
1137
|
}
|
|
1582
|
-
buildComposeRequest(
|
|
1583
|
-
const normalizedTime = this.computeTimelineTimestamp(
|
|
1138
|
+
buildComposeRequest(instruction, frame) {
|
|
1139
|
+
const normalizedTime = this.computeTimelineTimestamp(frame, instruction.baseConfig);
|
|
1584
1140
|
const clipStartUs = instruction.baseConfig.timeline?.clipStartUs ?? 0;
|
|
1585
1141
|
const clipDurationUs = instruction.baseConfig.timeline?.clipDurationUs ?? Infinity;
|
|
1586
1142
|
const clipEndUs = clipStartUs + clipDurationUs;
|
|
@@ -1621,43 +1177,40 @@ class VideoComposeWorker {
|
|
|
1621
1177
|
direction: entry.params.payload?.direction
|
|
1622
1178
|
};
|
|
1623
1179
|
}
|
|
1624
|
-
computeTimelineTimestamp(
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
if (!state) {
|
|
1628
|
-
state = {
|
|
1180
|
+
computeTimelineTimestamp(frame, config) {
|
|
1181
|
+
if (!this.streamState) {
|
|
1182
|
+
this.streamState = {
|
|
1629
1183
|
baseTimestamp: null,
|
|
1630
1184
|
lastSourceTimestamp: null,
|
|
1631
1185
|
nextFrameIndex: 0
|
|
1632
1186
|
};
|
|
1633
|
-
this.streamState.set(key, state);
|
|
1634
1187
|
}
|
|
1635
1188
|
const timeline = config.timeline;
|
|
1636
1189
|
if (!timeline) {
|
|
1637
1190
|
const ts = frame.timestamp ?? 0;
|
|
1638
|
-
|
|
1191
|
+
this.streamState.lastSourceTimestamp = frame.timestamp ?? null;
|
|
1639
1192
|
return ts;
|
|
1640
1193
|
}
|
|
1641
1194
|
const { clipStartUs, compositionFps } = timeline;
|
|
1642
1195
|
const sourceTimestamp = frame.timestamp ?? null;
|
|
1643
|
-
if (sourceTimestamp !== null &&
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
}
|
|
1647
|
-
if (
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
if (
|
|
1196
|
+
if (sourceTimestamp !== null && this.streamState.lastSourceTimestamp !== null && sourceTimestamp < this.streamState.lastSourceTimestamp) {
|
|
1197
|
+
this.streamState.baseTimestamp = null;
|
|
1198
|
+
this.streamState.nextFrameIndex = 0;
|
|
1199
|
+
}
|
|
1200
|
+
if (this.streamState.baseTimestamp === null) {
|
|
1201
|
+
this.streamState.baseTimestamp = sourceTimestamp ?? 0;
|
|
1202
|
+
this.streamState.nextFrameIndex = 0;
|
|
1203
|
+
if (this.streamState.baseTimestamp > 1e3) {
|
|
1651
1204
|
console.warn(
|
|
1652
|
-
`[VideoComposeWorker] First frame timestamp is ${
|
|
1205
|
+
`[VideoComposeWorker] First frame timestamp is ${this.streamState.baseTimestamp}us, expected ~0. Check MP4Demuxer normalization.`
|
|
1653
1206
|
);
|
|
1654
1207
|
}
|
|
1655
1208
|
}
|
|
1656
1209
|
const frameDuration = frameDurationFromFps(compositionFps);
|
|
1657
|
-
let frameIndex =
|
|
1210
|
+
let frameIndex = this.streamState.nextFrameIndex;
|
|
1658
1211
|
if (sourceTimestamp !== null) {
|
|
1659
1212
|
const approxIndex = frameIndexFromTimestamp(
|
|
1660
|
-
|
|
1213
|
+
this.streamState.baseTimestamp,
|
|
1661
1214
|
sourceTimestamp,
|
|
1662
1215
|
compositionFps,
|
|
1663
1216
|
"nearest"
|
|
@@ -1671,8 +1224,8 @@ class VideoComposeWorker {
|
|
|
1671
1224
|
compositionFps,
|
|
1672
1225
|
"nearest"
|
|
1673
1226
|
);
|
|
1674
|
-
|
|
1675
|
-
|
|
1227
|
+
this.streamState.nextFrameIndex = frameIndex + 1;
|
|
1228
|
+
this.streamState.lastSourceTimestamp = sourceTimestamp;
|
|
1676
1229
|
return timelineTime;
|
|
1677
1230
|
}
|
|
1678
1231
|
}
|
|
@@ -1680,4 +1233,9 @@ const worker = new VideoComposeWorker();
|
|
|
1680
1233
|
self.addEventListener("beforeunload", () => {
|
|
1681
1234
|
worker["handleDispose"]();
|
|
1682
1235
|
});
|
|
1683
|
-
|
|
1236
|
+
const videoCompose_worker = null;
|
|
1237
|
+
export {
|
|
1238
|
+
VideoComposeWorker,
|
|
1239
|
+
videoCompose_worker as default
|
|
1240
|
+
};
|
|
1241
|
+
//# sourceMappingURL=video-compose.worker.js.map
|