@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,1026 +0,0 @@
|
|
|
1
|
-
var WorkerMessageType = /* @__PURE__ */ ((WorkerMessageType2) => {
|
|
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
|
-
}
|
|
481
|
-
class BaseEncoder {
|
|
482
|
-
encoder;
|
|
483
|
-
config;
|
|
484
|
-
controller = null;
|
|
485
|
-
constructor(config) {
|
|
486
|
-
this.config = config;
|
|
487
|
-
}
|
|
488
|
-
getConfig() {
|
|
489
|
-
return { ...this.config };
|
|
490
|
-
}
|
|
491
|
-
get currentConfig() {
|
|
492
|
-
return this.config;
|
|
493
|
-
}
|
|
494
|
-
shouldReconfigure(partial) {
|
|
495
|
-
const next = { ...this.config, ...partial };
|
|
496
|
-
const keys = Object.keys(partial ?? {});
|
|
497
|
-
for (const key of keys) {
|
|
498
|
-
if (partial[key] !== void 0 && next[key] !== this.config[key]) {
|
|
499
|
-
return true;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
return false;
|
|
503
|
-
}
|
|
504
|
-
hasConfigChanged(next) {
|
|
505
|
-
const currentEntries = Object.entries(this.config);
|
|
506
|
-
for (const [key, value] of currentEntries) {
|
|
507
|
-
if (next[key] !== value) {
|
|
508
|
-
return true;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
for (const key of Object.keys(next)) {
|
|
512
|
-
if (this.config[key] !== next[key]) {
|
|
513
|
-
return true;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
return false;
|
|
517
|
-
}
|
|
518
|
-
configsEqual(a, b) {
|
|
519
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
520
|
-
}
|
|
521
|
-
async initialize() {
|
|
522
|
-
if (this.encoder?.state === "configured") {
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
const isSupported = await this.isConfigSupported(this.config);
|
|
526
|
-
if (!isSupported.supported) {
|
|
527
|
-
throw new Error(`Codec not supported: ${this.config.codec}`);
|
|
528
|
-
}
|
|
529
|
-
this.encoder = this.createEncoder({
|
|
530
|
-
output: this.handleOutput.bind(this),
|
|
531
|
-
error: this.handleError.bind(this)
|
|
532
|
-
});
|
|
533
|
-
this.encoder.configure(this.config);
|
|
534
|
-
}
|
|
535
|
-
async reconfigure(config) {
|
|
536
|
-
if (!config || Object.keys(config).length === 0) {
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
const nextConfig = { ...this.config, ...config };
|
|
540
|
-
if (this.configsEqual(this.config, nextConfig)) {
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
if (!this.encoder) {
|
|
544
|
-
this.config = nextConfig;
|
|
545
|
-
await this.initialize();
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
if (this.encoder.state === "configured") {
|
|
549
|
-
await this.encoder.flush();
|
|
550
|
-
}
|
|
551
|
-
const isSupported = await this.isConfigSupported(nextConfig);
|
|
552
|
-
if (!isSupported.supported) {
|
|
553
|
-
throw new Error(`New configuration not supported: ${nextConfig.codec}`);
|
|
554
|
-
}
|
|
555
|
-
this.config = nextConfig;
|
|
556
|
-
this.encoder.configure(this.config);
|
|
557
|
-
}
|
|
558
|
-
async flush() {
|
|
559
|
-
if (!this.encoder) {
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
await this.encoder.flush();
|
|
563
|
-
}
|
|
564
|
-
async reset() {
|
|
565
|
-
if (!this.encoder) {
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
this.encoder.reset();
|
|
569
|
-
this.onReset();
|
|
570
|
-
}
|
|
571
|
-
async close() {
|
|
572
|
-
if (!this.encoder) {
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
if (this.encoder.state === "configured") {
|
|
576
|
-
await this.encoder.flush();
|
|
577
|
-
}
|
|
578
|
-
this.encoder.close();
|
|
579
|
-
this.encoder = void 0;
|
|
580
|
-
}
|
|
581
|
-
get isReady() {
|
|
582
|
-
return this.encoder?.state === "configured";
|
|
583
|
-
}
|
|
584
|
-
get queueSize() {
|
|
585
|
-
return this.encoder?.encodeQueueSize ?? 0;
|
|
586
|
-
}
|
|
587
|
-
handleOutput(chunk, _metadata) {
|
|
588
|
-
this.controller?.enqueue(chunk);
|
|
589
|
-
}
|
|
590
|
-
handleError(error) {
|
|
591
|
-
console.error(`${this.getEncoderType()} encoder error:`, error);
|
|
592
|
-
this.controller?.error(error);
|
|
593
|
-
}
|
|
594
|
-
// Hook for subclasses to handle reset
|
|
595
|
-
onReset() {
|
|
596
|
-
}
|
|
597
|
-
/**
|
|
598
|
-
* Create transform stream for encoding
|
|
599
|
-
* Implements common stream logic with backpressure handling
|
|
600
|
-
*/
|
|
601
|
-
createStream() {
|
|
602
|
-
return new TransformStream(
|
|
603
|
-
{
|
|
604
|
-
start: async (controller) => {
|
|
605
|
-
this.controller = controller;
|
|
606
|
-
if (!this.isReady) {
|
|
607
|
-
await this.initialize();
|
|
608
|
-
}
|
|
609
|
-
},
|
|
610
|
-
transform: async (input) => {
|
|
611
|
-
if (!this.encoder || this.encoder.state !== "configured") {
|
|
612
|
-
throw new Error("Encoder not configured");
|
|
613
|
-
}
|
|
614
|
-
if (this.encoder.encodeQueueSize >= this.encodeQueueThreshold) {
|
|
615
|
-
await new Promise((resolve) => {
|
|
616
|
-
const check = () => {
|
|
617
|
-
if (!this.encoder || this.encoder.encodeQueueSize < this.encodeQueueThreshold - 1) {
|
|
618
|
-
resolve();
|
|
619
|
-
} else {
|
|
620
|
-
setTimeout(check, 10);
|
|
621
|
-
}
|
|
622
|
-
};
|
|
623
|
-
check();
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
this.encode(input);
|
|
627
|
-
},
|
|
628
|
-
flush: async () => {
|
|
629
|
-
await this.flush();
|
|
630
|
-
}
|
|
631
|
-
},
|
|
632
|
-
// Queuing strategy with backpressure configuration
|
|
633
|
-
{
|
|
634
|
-
highWaterMark: this.highWaterMark,
|
|
635
|
-
size: () => 1
|
|
636
|
-
// Count-based
|
|
637
|
-
}
|
|
638
|
-
);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
class VideoChunkEncoder extends BaseEncoder {
|
|
642
|
-
static DEFAULT_HIGH_WATER_MARK = 2;
|
|
643
|
-
static DEFAULT_ENCODE_QUEUE_THRESHOLD = 8;
|
|
644
|
-
highWaterMark;
|
|
645
|
-
encodeQueueThreshold;
|
|
646
|
-
frameCount = 0;
|
|
647
|
-
keyFrameInterval = 60;
|
|
648
|
-
// 2 seconds at 30fps
|
|
649
|
-
constructor(config) {
|
|
650
|
-
super(config);
|
|
651
|
-
this.highWaterMark = config.backpressure?.highWaterMark ?? VideoChunkEncoder.DEFAULT_HIGH_WATER_MARK;
|
|
652
|
-
this.encodeQueueThreshold = config.backpressure?.encodeQueueThreshold ?? VideoChunkEncoder.DEFAULT_ENCODE_QUEUE_THRESHOLD;
|
|
653
|
-
}
|
|
654
|
-
async isConfigSupported(config) {
|
|
655
|
-
const result = await VideoEncoder.isConfigSupported(config);
|
|
656
|
-
return { supported: result.supported ?? false };
|
|
657
|
-
}
|
|
658
|
-
createEncoder(init) {
|
|
659
|
-
return new VideoEncoder(init);
|
|
660
|
-
}
|
|
661
|
-
getEncoderType() {
|
|
662
|
-
return "Video";
|
|
663
|
-
}
|
|
664
|
-
onReset() {
|
|
665
|
-
this.frameCount = 0;
|
|
666
|
-
}
|
|
667
|
-
encode(frame) {
|
|
668
|
-
const keyFrame = this.shouldGenerateKeyFrame();
|
|
669
|
-
const encodeOptions = {
|
|
670
|
-
keyFrame
|
|
671
|
-
};
|
|
672
|
-
this.encoder.encode(frame, encodeOptions);
|
|
673
|
-
this.frameCount++;
|
|
674
|
-
frame.close();
|
|
675
|
-
}
|
|
676
|
-
setKeyFrameInterval(interval) {
|
|
677
|
-
this.keyFrameInterval = Math.max(1, interval);
|
|
678
|
-
}
|
|
679
|
-
shouldGenerateKeyFrame() {
|
|
680
|
-
if (this.frameCount === 0) {
|
|
681
|
-
return true;
|
|
682
|
-
}
|
|
683
|
-
return this.frameCount % this.keyFrameInterval === 0;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
class AudioChunkEncoder extends BaseEncoder {
|
|
687
|
-
static DEFAULT_HIGH_WATER_MARK = 4;
|
|
688
|
-
static DEFAULT_ENCODE_QUEUE_THRESHOLD = 16;
|
|
689
|
-
highWaterMark;
|
|
690
|
-
encodeQueueThreshold;
|
|
691
|
-
constructor(config) {
|
|
692
|
-
super(config);
|
|
693
|
-
this.highWaterMark = config.backpressure?.highWaterMark ?? AudioChunkEncoder.DEFAULT_HIGH_WATER_MARK;
|
|
694
|
-
this.encodeQueueThreshold = config.backpressure?.encodeQueueThreshold ?? AudioChunkEncoder.DEFAULT_ENCODE_QUEUE_THRESHOLD;
|
|
695
|
-
}
|
|
696
|
-
async isConfigSupported(config) {
|
|
697
|
-
const result = await AudioEncoder.isConfigSupported(config);
|
|
698
|
-
return { supported: result.supported ?? false };
|
|
699
|
-
}
|
|
700
|
-
createEncoder(init) {
|
|
701
|
-
return new AudioEncoder(init);
|
|
702
|
-
}
|
|
703
|
-
getEncoderType() {
|
|
704
|
-
return "Audio";
|
|
705
|
-
}
|
|
706
|
-
encode(data) {
|
|
707
|
-
if (this.encoder?.state !== "configured") {
|
|
708
|
-
throw new Error("Audio encoder not configured");
|
|
709
|
-
}
|
|
710
|
-
const config = this.getConfig();
|
|
711
|
-
if (data.sampleRate !== config.sampleRate || data.numberOfChannels !== config.numberOfChannels) {
|
|
712
|
-
throw new Error("AudioData requires resampling or channel remap before encoding");
|
|
713
|
-
}
|
|
714
|
-
this.encoder.encode(data);
|
|
715
|
-
data.close();
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
class EncodeWorker {
|
|
719
|
-
channel;
|
|
720
|
-
videoEncoder = null;
|
|
721
|
-
audioEncoder = null;
|
|
722
|
-
// Connections to other workers
|
|
723
|
-
cachePort = null;
|
|
724
|
-
muxPort = null;
|
|
725
|
-
composePorts = /* @__PURE__ */ new Map();
|
|
726
|
-
// Connections from compose workers
|
|
727
|
-
constructor() {
|
|
728
|
-
this.channel = new WorkerChannel(self, {
|
|
729
|
-
name: "EncodeWorker",
|
|
730
|
-
timeout: 3e4
|
|
731
|
-
});
|
|
732
|
-
this.setupHandlers();
|
|
733
|
-
}
|
|
734
|
-
setupHandlers() {
|
|
735
|
-
this.channel.registerHandler("configure", this.handleConfigure.bind(this));
|
|
736
|
-
this.channel.registerHandler("connect", this.handleConnect.bind(this));
|
|
737
|
-
this.channel.registerHandler("configure_video", this.handleConfigureVideo.bind(this));
|
|
738
|
-
this.channel.registerHandler("configure_audio", this.handleConfigureAudio.bind(this));
|
|
739
|
-
this.channel.registerHandler("flush", this.handleFlush.bind(this));
|
|
740
|
-
this.channel.registerHandler("reset", this.handleReset.bind(this));
|
|
741
|
-
this.channel.registerHandler("get_stats", this.handleGetStats.bind(this));
|
|
742
|
-
this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));
|
|
743
|
-
}
|
|
744
|
-
/**
|
|
745
|
-
* Connect handler used by stream pipeline
|
|
746
|
-
*/
|
|
747
|
-
async handleConnect(payload) {
|
|
748
|
-
const { port, streamType } = payload;
|
|
749
|
-
if (streamType === "video") return this.handleConnectComposer({ composeType: "video", port });
|
|
750
|
-
if (streamType === "audio") return this.handleConnectComposer({ composeType: "audio", port });
|
|
751
|
-
if (streamType === "chunk") return this.handleConnectMux({ port });
|
|
752
|
-
return { success: true };
|
|
753
|
-
}
|
|
754
|
-
/**
|
|
755
|
-
* Handle configuration message from orchestrator
|
|
756
|
-
* @param payload.initial - If true, initialize worker and recreate encoder instances; otherwise just update config
|
|
757
|
-
*/
|
|
758
|
-
async handleConfigure(payload) {
|
|
759
|
-
const { config, initial = false } = payload;
|
|
760
|
-
if (initial) {
|
|
761
|
-
this.channel.state = WorkerState.Ready;
|
|
762
|
-
}
|
|
763
|
-
if (config.video) {
|
|
764
|
-
if (initial || !this.videoEncoder) {
|
|
765
|
-
if (this.videoEncoder) {
|
|
766
|
-
await this.videoEncoder.close();
|
|
767
|
-
}
|
|
768
|
-
this.videoEncoder = new VideoChunkEncoder(config.video);
|
|
769
|
-
await this.videoEncoder.initialize();
|
|
770
|
-
const videoStream = config.video.stream?.pipeThrough(this.videoEncoder.createStream());
|
|
771
|
-
if (videoStream && this.cachePort) {
|
|
772
|
-
const cacheChannel = new WorkerChannel(this.cachePort, {
|
|
773
|
-
name: "Encode-Cache-Video",
|
|
774
|
-
timeout: 3e4
|
|
775
|
-
});
|
|
776
|
-
await cacheChannel.sendStream(videoStream, {
|
|
777
|
-
type: "video",
|
|
778
|
-
width: config.video.width,
|
|
779
|
-
height: config.video.height,
|
|
780
|
-
framerate: config.video.framerate
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
} else {
|
|
784
|
-
await this.videoEncoder.reconfigure(config.video);
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
if (config.audio) {
|
|
788
|
-
if (initial || !this.audioEncoder) {
|
|
789
|
-
if (this.audioEncoder) {
|
|
790
|
-
await this.audioEncoder.close();
|
|
791
|
-
}
|
|
792
|
-
this.audioEncoder = new AudioChunkEncoder(config.audio);
|
|
793
|
-
await this.audioEncoder.initialize();
|
|
794
|
-
const audioStream = config.audio.stream?.pipeThrough(this.audioEncoder.createStream());
|
|
795
|
-
if (audioStream && this.cachePort) {
|
|
796
|
-
const cacheChannel = new WorkerChannel(this.cachePort, {
|
|
797
|
-
name: "Encode-Cache-Audio",
|
|
798
|
-
timeout: 3e4
|
|
799
|
-
});
|
|
800
|
-
await cacheChannel.sendStream(audioStream, {
|
|
801
|
-
type: "audio",
|
|
802
|
-
sampleRate: config.audio.sampleRate,
|
|
803
|
-
numberOfChannels: config.audio.numberOfChannels
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
|
-
} else {
|
|
807
|
-
await this.audioEncoder.reconfigure(config.audio);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
return { success: true };
|
|
811
|
-
}
|
|
812
|
-
/**
|
|
813
|
-
* Connect to a compose worker to receive frames/audio data
|
|
814
|
-
*/
|
|
815
|
-
async handleConnectComposer(payload) {
|
|
816
|
-
const { composeType, port } = payload;
|
|
817
|
-
this.composePorts.set(composeType, port);
|
|
818
|
-
const composeChannel = new WorkerChannel(port, {
|
|
819
|
-
name: `Encode-${composeType}Compose`,
|
|
820
|
-
timeout: 3e4
|
|
821
|
-
});
|
|
822
|
-
composeChannel.receiveStream(async (stream, metadata) => {
|
|
823
|
-
if (metadata?.streamType === "video" && this.videoEncoder) {
|
|
824
|
-
const reader = stream.getReader();
|
|
825
|
-
try {
|
|
826
|
-
while (true) {
|
|
827
|
-
const { done, value } = await reader.read();
|
|
828
|
-
if (done) break;
|
|
829
|
-
const wrappedValue = value;
|
|
830
|
-
const videoFrame = wrappedValue.frame || wrappedValue;
|
|
831
|
-
try {
|
|
832
|
-
const frame = videoFrame.clone();
|
|
833
|
-
this.videoEncoder.encode(frame);
|
|
834
|
-
} finally {
|
|
835
|
-
videoFrame.close();
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
} finally {
|
|
839
|
-
reader.releaseLock();
|
|
840
|
-
}
|
|
841
|
-
} else if (metadata?.streamType === "audio" && this.audioEncoder) {
|
|
842
|
-
const composedConfig = {
|
|
843
|
-
sampleRate: metadata.sampleRate,
|
|
844
|
-
numberOfChannels: metadata.numberOfChannels
|
|
845
|
-
};
|
|
846
|
-
const currentConfig = this.audioEncoder.getConfig();
|
|
847
|
-
if (typeof composedConfig.sampleRate === "number" && composedConfig.sampleRate > 0 && composedConfig.sampleRate !== currentConfig.sampleRate) {
|
|
848
|
-
await this.audioEncoder.reconfigure({ sampleRate: composedConfig.sampleRate });
|
|
849
|
-
}
|
|
850
|
-
if (typeof composedConfig.numberOfChannels === "number" && composedConfig.numberOfChannels > 0 && composedConfig.numberOfChannels !== currentConfig.numberOfChannels) {
|
|
851
|
-
await this.audioEncoder.reconfigure({
|
|
852
|
-
numberOfChannels: composedConfig.numberOfChannels
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
|
-
const reader = stream.getReader();
|
|
856
|
-
try {
|
|
857
|
-
while (true) {
|
|
858
|
-
const { done, value } = await reader.read();
|
|
859
|
-
if (done) break;
|
|
860
|
-
this.audioEncoder.encode(value);
|
|
861
|
-
}
|
|
862
|
-
} finally {
|
|
863
|
-
reader.releaseLock();
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
});
|
|
867
|
-
return { success: true };
|
|
868
|
-
}
|
|
869
|
-
/**
|
|
870
|
-
* Connect to cache manager for output streaming
|
|
871
|
-
*/
|
|
872
|
-
// private async handleConnectCache(payload: { port: MessagePort }): Promise<{ success: boolean }> {
|
|
873
|
-
// this.cachePort = payload.port;
|
|
874
|
-
// return { success: true };
|
|
875
|
-
// }
|
|
876
|
-
/**
|
|
877
|
-
* Connect to mux worker for output streaming
|
|
878
|
-
*/
|
|
879
|
-
async handleConnectMux(payload) {
|
|
880
|
-
this.muxPort = payload.port;
|
|
881
|
-
return { success: true };
|
|
882
|
-
}
|
|
883
|
-
/**
|
|
884
|
-
* Configure video encoder with specific settings
|
|
885
|
-
*/
|
|
886
|
-
async handleConfigureVideo(config) {
|
|
887
|
-
try {
|
|
888
|
-
if (!this.videoEncoder) {
|
|
889
|
-
this.videoEncoder = new VideoChunkEncoder(config);
|
|
890
|
-
await this.videoEncoder.initialize();
|
|
891
|
-
} else {
|
|
892
|
-
await this.videoEncoder.reconfigure(config);
|
|
893
|
-
}
|
|
894
|
-
this.channel.notify("video_configured", {
|
|
895
|
-
codec: config.codec,
|
|
896
|
-
width: config.width,
|
|
897
|
-
height: config.height,
|
|
898
|
-
bitrate: config.bitrate
|
|
899
|
-
});
|
|
900
|
-
return { success: true };
|
|
901
|
-
} catch (error) {
|
|
902
|
-
throw {
|
|
903
|
-
code: "VIDEO_CONFIG_ERROR",
|
|
904
|
-
message: error.message
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
/**
|
|
909
|
-
* Configure audio encoder with specific settings
|
|
910
|
-
*/
|
|
911
|
-
async handleConfigureAudio(config) {
|
|
912
|
-
try {
|
|
913
|
-
if (!this.audioEncoder) {
|
|
914
|
-
this.audioEncoder = new AudioChunkEncoder(config);
|
|
915
|
-
await this.audioEncoder.initialize();
|
|
916
|
-
} else {
|
|
917
|
-
await this.audioEncoder.reconfigure(config);
|
|
918
|
-
}
|
|
919
|
-
this.channel.notify("audio_configured", {
|
|
920
|
-
codec: config.codec,
|
|
921
|
-
sampleRate: config.sampleRate,
|
|
922
|
-
numberOfChannels: config.numberOfChannels,
|
|
923
|
-
bitrate: config.bitrate
|
|
924
|
-
});
|
|
925
|
-
return { success: true };
|
|
926
|
-
} catch (error) {
|
|
927
|
-
throw {
|
|
928
|
-
code: "AUDIO_CONFIG_ERROR",
|
|
929
|
-
message: error.message
|
|
930
|
-
};
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
/**
|
|
934
|
-
* Flush encoders and get buffered chunks
|
|
935
|
-
*/
|
|
936
|
-
async handleFlush(payload) {
|
|
937
|
-
try {
|
|
938
|
-
const result = {};
|
|
939
|
-
if (!payload?.type || payload.type === "video") {
|
|
940
|
-
const chunks = await this.videoEncoder?.flush();
|
|
941
|
-
if (chunks) {
|
|
942
|
-
result.videoChunks = chunks;
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
if (!payload?.type || payload.type === "audio") {
|
|
946
|
-
const chunks = await this.audioEncoder?.flush();
|
|
947
|
-
if (chunks) {
|
|
948
|
-
result.audioChunks = chunks;
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
return result;
|
|
952
|
-
} catch (error) {
|
|
953
|
-
throw {
|
|
954
|
-
code: "FLUSH_ERROR",
|
|
955
|
-
message: error.message
|
|
956
|
-
};
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
/**
|
|
960
|
-
* Reset encoders
|
|
961
|
-
*/
|
|
962
|
-
async handleReset(payload) {
|
|
963
|
-
try {
|
|
964
|
-
if (!payload?.type || payload.type === "video") {
|
|
965
|
-
await this.videoEncoder?.reset();
|
|
966
|
-
}
|
|
967
|
-
if (!payload?.type || payload.type === "audio") {
|
|
968
|
-
await this.audioEncoder?.reset();
|
|
969
|
-
}
|
|
970
|
-
this.channel.notify("reset_complete", {
|
|
971
|
-
type: payload?.type || "all"
|
|
972
|
-
});
|
|
973
|
-
return { success: true };
|
|
974
|
-
} catch (error) {
|
|
975
|
-
throw {
|
|
976
|
-
code: "RESET_ERROR",
|
|
977
|
-
message: error.message
|
|
978
|
-
};
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
/**
|
|
982
|
-
* Get encoder statistics
|
|
983
|
-
*/
|
|
984
|
-
async handleGetStats() {
|
|
985
|
-
const stats = {};
|
|
986
|
-
if (this.videoEncoder) {
|
|
987
|
-
stats.video = {
|
|
988
|
-
configured: this.videoEncoder.isReady,
|
|
989
|
-
queueSize: this.videoEncoder.queueSize
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
if (this.audioEncoder) {
|
|
993
|
-
stats.audio = {
|
|
994
|
-
configured: this.audioEncoder.isReady,
|
|
995
|
-
queueSize: this.audioEncoder.queueSize
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
return stats;
|
|
999
|
-
}
|
|
1000
|
-
// Output and error handling is done via streams in the encoder itself
|
|
1001
|
-
// These placeholder methods can be implemented when needed for direct callback handling
|
|
1002
|
-
/**
|
|
1003
|
-
* Dispose worker and cleanup resources
|
|
1004
|
-
*/
|
|
1005
|
-
async handleDispose() {
|
|
1006
|
-
await this.videoEncoder?.close();
|
|
1007
|
-
await this.audioEncoder?.close();
|
|
1008
|
-
this.videoEncoder = null;
|
|
1009
|
-
this.audioEncoder = null;
|
|
1010
|
-
this.cachePort?.close();
|
|
1011
|
-
this.cachePort = null;
|
|
1012
|
-
this.muxPort?.close();
|
|
1013
|
-
this.muxPort = null;
|
|
1014
|
-
for (const port of this.composePorts.values()) {
|
|
1015
|
-
port.close();
|
|
1016
|
-
}
|
|
1017
|
-
this.composePorts.clear();
|
|
1018
|
-
this.channel.state = WorkerState.Disposed;
|
|
1019
|
-
return { success: true };
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
const worker = new EncodeWorker();
|
|
1023
|
-
self.addEventListener("beforeunload", () => {
|
|
1024
|
-
worker["handleDispose"]();
|
|
1025
|
-
});
|
|
1026
|
-
//# sourceMappingURL=encode.worker-nfOb3kw6.js.map
|