@meframe/core 0.0.1
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/CHANGELOG.md +42 -0
- package/LICENSE +22 -0
- package/README.md +396 -0
- package/dist/Meframe.d.ts +82 -0
- package/dist/Meframe.d.ts.map +1 -0
- package/dist/Meframe.js +290 -0
- package/dist/Meframe.js.map +1 -0
- package/dist/_virtual/mp4box.all.js +5 -0
- package/dist/_virtual/mp4box.all.js.map +1 -0
- package/dist/cache/BatchWriter.d.ts +25 -0
- package/dist/cache/BatchWriter.d.ts.map +1 -0
- package/dist/cache/CacheManager.d.ts +115 -0
- package/dist/cache/CacheManager.d.ts.map +1 -0
- package/dist/cache/CacheManager.js +388 -0
- package/dist/cache/CacheManager.js.map +1 -0
- package/dist/cache/CacheStatsDecorator.d.ts +27 -0
- package/dist/cache/CacheStatsDecorator.d.ts.map +1 -0
- package/dist/cache/L2Cache.d.ts +39 -0
- package/dist/cache/L2Cache.d.ts.map +1 -0
- package/dist/cache/L2Cache.js +282 -0
- package/dist/cache/L2Cache.js.map +1 -0
- package/dist/cache/index.d.ts +7 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/l1/AudioL1Cache.d.ts +30 -0
- package/dist/cache/l1/AudioL1Cache.d.ts.map +1 -0
- package/dist/cache/l1/AudioL1Cache.js +306 -0
- package/dist/cache/l1/AudioL1Cache.js.map +1 -0
- package/dist/cache/l1/MixedAudioL1Cache.d.ts +13 -0
- package/dist/cache/l1/MixedAudioL1Cache.d.ts.map +1 -0
- package/dist/cache/l1/MixedAudioL1Cache.js +52 -0
- package/dist/cache/l1/MixedAudioL1Cache.js.map +1 -0
- package/dist/cache/l1/VideoL1Cache.d.ts +69 -0
- package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -0
- package/dist/cache/l1/VideoL1Cache.js +318 -0
- package/dist/cache/l1/VideoL1Cache.js.map +1 -0
- package/dist/cache/l1/gop-utils.d.ts +10 -0
- package/dist/cache/l1/gop-utils.d.ts.map +1 -0
- package/dist/cache/l1/gop-utils.js +78 -0
- package/dist/cache/l1/gop-utils.js.map +1 -0
- package/dist/cache/l1/index.d.ts +4 -0
- package/dist/cache/l1/index.d.ts.map +1 -0
- package/dist/cache/l1/types.d.ts +17 -0
- package/dist/cache/l1/types.d.ts.map +1 -0
- package/dist/cache/types.d.ts +93 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/config/ConfigLoader.d.ts +69 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +133 -0
- package/dist/config/ConfigLoader.js.map +1 -0
- package/dist/config/defaults.d.ts +125 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +191 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/presets.d.ts +32 -0
- package/dist/config/presets.d.ts.map +1 -0
- package/dist/config/presets.js +11 -0
- package/dist/config/presets.js.map +1 -0
- package/dist/config/types.d.ts +199 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/validation.d.ts +19 -0
- package/dist/config/validation.d.ts.map +1 -0
- package/dist/config/validation.js +232 -0
- package/dist/config/validation.js.map +1 -0
- package/dist/controllers/PlaybackController.d.ts +55 -0
- package/dist/controllers/PlaybackController.d.ts.map +1 -0
- package/dist/controllers/PlaybackController.js +369 -0
- package/dist/controllers/PlaybackController.js.map +1 -0
- package/dist/controllers/PreRenderService.d.ts +34 -0
- package/dist/controllers/PreRenderService.d.ts.map +1 -0
- package/dist/controllers/PreRenderService.js +83 -0
- package/dist/controllers/PreRenderService.js.map +1 -0
- package/dist/controllers/PreRenderTaskQueue.d.ts +21 -0
- package/dist/controllers/PreRenderTaskQueue.d.ts.map +1 -0
- package/dist/controllers/PreviewHandle.d.ts +23 -0
- package/dist/controllers/PreviewHandle.d.ts.map +1 -0
- package/dist/controllers/PreviewHandle.js +39 -0
- package/dist/controllers/PreviewHandle.js.map +1 -0
- package/dist/controllers/index.d.ts +8 -0
- package/dist/controllers/index.d.ts.map +1 -0
- package/dist/controllers/types.d.ts +102 -0
- package/dist/controllers/types.d.ts.map +1 -0
- package/dist/event/EventBus.d.ts +42 -0
- package/dist/event/EventBus.d.ts.map +1 -0
- package/dist/event/EventBus.js +94 -0
- package/dist/event/EventBus.js.map +1 -0
- package/dist/event/events.d.ts +371 -0
- package/dist/event/events.d.ts.map +1 -0
- package/dist/event/events.js +71 -0
- package/dist/event/events.js.map +1 -0
- package/dist/event/index.d.ts +4 -0
- package/dist/event/index.d.ts.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/model/CompositionModel.d.ts +48 -0
- package/dist/model/CompositionModel.d.ts.map +1 -0
- package/dist/model/CompositionModel.js +197 -0
- package/dist/model/CompositionModel.js.map +1 -0
- package/dist/model/RcFrame.d.ts +34 -0
- package/dist/model/RcFrame.d.ts.map +1 -0
- package/dist/model/RcFrame.js +97 -0
- package/dist/model/RcFrame.js.map +1 -0
- package/dist/model/dirty-range.d.ts +5 -0
- package/dist/model/dirty-range.d.ts.map +1 -0
- package/dist/model/dirty-range.js +220 -0
- package/dist/model/dirty-range.js.map +1 -0
- package/dist/model/index.d.ts +7 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/patch.d.ts +5 -0
- package/dist/model/patch.d.ts.map +1 -0
- package/dist/model/patch.js +250 -0
- package/dist/model/patch.js.map +1 -0
- package/dist/model/types.d.ts +135 -0
- package/dist/model/types.d.ts.map +1 -0
- package/dist/model/types.js +5 -0
- package/dist/model/types.js.map +1 -0
- package/dist/model/validation.d.ts +15 -0
- package/dist/model/validation.d.ts.map +1 -0
- package/dist/model/validation.js +74 -0
- package/dist/model/validation.js.map +1 -0
- package/dist/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js +7046 -0
- package/dist/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js.map +1 -0
- package/dist/orchestrator/ClipSessionManager.d.ts +75 -0
- package/dist/orchestrator/ClipSessionManager.d.ts.map +1 -0
- package/dist/orchestrator/ClipSessionManager.js +160 -0
- package/dist/orchestrator/ClipSessionManager.js.map +1 -0
- package/dist/orchestrator/CompositionPlanner.d.ts +55 -0
- package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -0
- package/dist/orchestrator/CompositionPlanner.js +411 -0
- package/dist/orchestrator/CompositionPlanner.js.map +1 -0
- package/dist/orchestrator/Orchestrator.d.ts +59 -0
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -0
- package/dist/orchestrator/Orchestrator.js +390 -0
- package/dist/orchestrator/Orchestrator.js.map +1 -0
- package/dist/orchestrator/VideoClipSession.d.ts +64 -0
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -0
- package/dist/orchestrator/VideoClipSession.js +309 -0
- package/dist/orchestrator/VideoClipSession.js.map +1 -0
- package/dist/orchestrator/index.d.ts +5 -0
- package/dist/orchestrator/index.d.ts.map +1 -0
- package/dist/orchestrator/types.d.ts +64 -0
- package/dist/orchestrator/types.d.ts.map +1 -0
- package/dist/plugins/BackpressureMonitor.d.ts +33 -0
- package/dist/plugins/BackpressureMonitor.d.ts.map +1 -0
- package/dist/plugins/BackpressureMonitor.js +62 -0
- package/dist/plugins/BackpressureMonitor.js.map +1 -0
- package/dist/plugins/PluginManager.d.ts +37 -0
- package/dist/plugins/PluginManager.d.ts.map +1 -0
- package/dist/plugins/PluginManager.js +66 -0
- package/dist/plugins/PluginManager.js.map +1 -0
- package/dist/plugins/types.d.ts +60 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/stages/compose/AudioDucker.d.ts +59 -0
- package/dist/stages/compose/AudioDucker.d.ts.map +1 -0
- package/dist/stages/compose/AudioDucker.js +161 -0
- package/dist/stages/compose/AudioDucker.js.map +1 -0
- package/dist/stages/compose/AudioMixer.d.ts +29 -0
- package/dist/stages/compose/AudioMixer.d.ts.map +1 -0
- package/dist/stages/compose/AudioMixer.js +373 -0
- package/dist/stages/compose/AudioMixer.js.map +1 -0
- package/dist/stages/compose/FilterProcessor.d.ts +41 -0
- package/dist/stages/compose/FilterProcessor.d.ts.map +1 -0
- package/dist/stages/compose/FilterProcessor.js +226 -0
- package/dist/stages/compose/FilterProcessor.js.map +1 -0
- package/dist/stages/compose/GlobalAudioSession.d.ts +38 -0
- package/dist/stages/compose/GlobalAudioSession.d.ts.map +1 -0
- package/dist/stages/compose/GlobalAudioSession.js +122 -0
- package/dist/stages/compose/GlobalAudioSession.js.map +1 -0
- package/dist/stages/compose/LayerRenderer.d.ts +30 -0
- package/dist/stages/compose/LayerRenderer.d.ts.map +1 -0
- package/dist/stages/compose/LayerRenderer.js +215 -0
- package/dist/stages/compose/LayerRenderer.js.map +1 -0
- package/dist/stages/compose/OfflineAudioMixer.d.ts +14 -0
- package/dist/stages/compose/OfflineAudioMixer.d.ts.map +1 -0
- package/dist/stages/compose/OfflineAudioMixer.js +68 -0
- package/dist/stages/compose/OfflineAudioMixer.js.map +1 -0
- package/dist/stages/compose/TransitionProcessor.d.ts +30 -0
- package/dist/stages/compose/TransitionProcessor.d.ts.map +1 -0
- package/dist/stages/compose/TransitionProcessor.js +189 -0
- package/dist/stages/compose/TransitionProcessor.js.map +1 -0
- package/dist/stages/compose/VideoComposer.d.ts +30 -0
- package/dist/stages/compose/VideoComposer.d.ts.map +1 -0
- package/dist/stages/compose/VideoComposer.js +186 -0
- package/dist/stages/compose/VideoComposer.js.map +1 -0
- package/dist/stages/compose/audio-compose.worker.d.ts +79 -0
- package/dist/stages/compose/audio-compose.worker.d.ts.map +1 -0
- package/dist/stages/compose/audio-compose.worker.js +541 -0
- package/dist/stages/compose/audio-compose.worker.js.map +1 -0
- package/dist/stages/compose/instructions.d.ts +95 -0
- package/dist/stages/compose/instructions.d.ts.map +1 -0
- package/dist/stages/compose/types.d.ts +245 -0
- package/dist/stages/compose/types.d.ts.map +1 -0
- package/dist/stages/compose/video-compose.worker.d.ts +60 -0
- package/dist/stages/compose/video-compose.worker.d.ts.map +1 -0
- package/dist/stages/compose/video-compose.worker.js +369 -0
- package/dist/stages/compose/video-compose.worker.js.map +1 -0
- package/dist/stages/decode/AudioChunkDecoder.d.ts +41 -0
- package/dist/stages/decode/AudioChunkDecoder.d.ts.map +1 -0
- package/dist/stages/decode/AudioChunkDecoder.js +83 -0
- package/dist/stages/decode/AudioChunkDecoder.js.map +1 -0
- package/dist/stages/decode/BaseDecoder.d.ts +35 -0
- package/dist/stages/decode/BaseDecoder.d.ts.map +1 -0
- package/dist/stages/decode/BaseDecoder.js +130 -0
- package/dist/stages/decode/BaseDecoder.js.map +1 -0
- package/dist/stages/decode/VideoChunkDecoder.d.ts +54 -0
- package/dist/stages/decode/VideoChunkDecoder.d.ts.map +1 -0
- package/dist/stages/decode/VideoChunkDecoder.js +209 -0
- package/dist/stages/decode/VideoChunkDecoder.js.map +1 -0
- package/dist/stages/decode/decode.worker.d.ts +70 -0
- package/dist/stages/decode/decode.worker.d.ts.map +1 -0
- package/dist/stages/decode/decode.worker.js +436 -0
- package/dist/stages/decode/decode.worker.js.map +1 -0
- package/dist/stages/decode/index.d.ts +5 -0
- package/dist/stages/decode/index.d.ts.map +1 -0
- package/dist/stages/decode/types.d.ts +108 -0
- package/dist/stages/decode/types.d.ts.map +1 -0
- package/dist/stages/demux/MP3FrameParser.d.ts +33 -0
- package/dist/stages/demux/MP3FrameParser.d.ts.map +1 -0
- package/dist/stages/demux/MP3FrameParser.js +186 -0
- package/dist/stages/demux/MP3FrameParser.js.map +1 -0
- package/dist/stages/demux/MP4Demuxer.d.ts +45 -0
- package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -0
- package/dist/stages/demux/MP4Demuxer.js +227 -0
- package/dist/stages/demux/MP4Demuxer.js.map +1 -0
- package/dist/stages/demux/aac-esds-extractor.d.ts +7 -0
- package/dist/stages/demux/aac-esds-extractor.d.ts.map +1 -0
- package/dist/stages/demux/audio-demux.worker.d.ts +51 -0
- package/dist/stages/demux/audio-demux.worker.d.ts.map +1 -0
- package/dist/stages/demux/audio-demux.worker.js +312 -0
- package/dist/stages/demux/audio-demux.worker.js.map +1 -0
- package/dist/stages/demux/types.d.ts +77 -0
- package/dist/stages/demux/types.d.ts.map +1 -0
- package/dist/stages/demux/video-demux.worker.d.ts +48 -0
- package/dist/stages/demux/video-demux.worker.d.ts.map +1 -0
- package/dist/stages/demux/video-demux.worker.js +173 -0
- package/dist/stages/demux/video-demux.worker.js.map +1 -0
- package/dist/stages/encode/AudioChunkEncoder.d.ts +21 -0
- package/dist/stages/encode/AudioChunkEncoder.d.ts.map +1 -0
- package/dist/stages/encode/AudioChunkEncoder.js +37 -0
- package/dist/stages/encode/AudioChunkEncoder.js.map +1 -0
- package/dist/stages/encode/BaseEncoder.d.ts +44 -0
- package/dist/stages/encode/BaseEncoder.d.ts.map +1 -0
- package/dist/stages/encode/BaseEncoder.js +164 -0
- package/dist/stages/encode/BaseEncoder.js.map +1 -0
- package/dist/stages/encode/EncoderPool.d.ts +28 -0
- package/dist/stages/encode/EncoderPool.d.ts.map +1 -0
- package/dist/stages/encode/VideoChunkEncoder.d.ts +26 -0
- package/dist/stages/encode/VideoChunkEncoder.d.ts.map +1 -0
- package/dist/stages/encode/VideoChunkEncoder.js +50 -0
- package/dist/stages/encode/VideoChunkEncoder.js.map +1 -0
- package/dist/stages/encode/encode.worker.d.ts +3 -0
- package/dist/stages/encode/encode.worker.d.ts.map +1 -0
- package/dist/stages/encode/encode.worker.js +318 -0
- package/dist/stages/encode/encode.worker.js.map +1 -0
- package/dist/stages/encode/index.d.ts +6 -0
- package/dist/stages/encode/index.d.ts.map +1 -0
- package/dist/stages/encode/types.d.ts +127 -0
- package/dist/stages/encode/types.d.ts.map +1 -0
- package/dist/stages/load/EventHandlers.d.ts +35 -0
- package/dist/stages/load/EventHandlers.d.ts.map +1 -0
- package/dist/stages/load/EventHandlers.js +65 -0
- package/dist/stages/load/EventHandlers.js.map +1 -0
- package/dist/stages/load/ResourceLoader.d.ts +36 -0
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -0
- package/dist/stages/load/ResourceLoader.js +184 -0
- package/dist/stages/load/ResourceLoader.js.map +1 -0
- package/dist/stages/load/StreamFactory.d.ts +42 -0
- package/dist/stages/load/StreamFactory.d.ts.map +1 -0
- package/dist/stages/load/StreamFactory.js +201 -0
- package/dist/stages/load/StreamFactory.js.map +1 -0
- package/dist/stages/load/TaskManager.d.ts +50 -0
- package/dist/stages/load/TaskManager.d.ts.map +1 -0
- package/dist/stages/load/TaskManager.js +103 -0
- package/dist/stages/load/TaskManager.js.map +1 -0
- package/dist/stages/load/WindowByteRangeResolver.d.ts +47 -0
- package/dist/stages/load/WindowByteRangeResolver.d.ts.map +1 -0
- package/dist/stages/load/WindowByteRangeResolver.js +270 -0
- package/dist/stages/load/WindowByteRangeResolver.js.map +1 -0
- package/dist/stages/load/index.d.ts +11 -0
- package/dist/stages/load/index.d.ts.map +1 -0
- package/dist/stages/load/types.d.ts +177 -0
- package/dist/stages/load/types.d.ts.map +1 -0
- package/dist/stages/mux/MP4Muxer.d.ts +44 -0
- package/dist/stages/mux/MP4Muxer.d.ts.map +1 -0
- package/dist/stages/mux/MP4Muxer.js +262 -0
- package/dist/stages/mux/MP4Muxer.js.map +1 -0
- package/dist/stages/mux/OPFSWriter.d.ts +46 -0
- package/dist/stages/mux/OPFSWriter.d.ts.map +1 -0
- package/dist/stages/mux/index.d.ts +5 -0
- package/dist/stages/mux/index.d.ts.map +1 -0
- package/dist/stages/mux/mux.worker.d.ts +65 -0
- package/dist/stages/mux/mux.worker.d.ts.map +1 -0
- package/dist/stages/mux/mux.worker.js +219 -0
- package/dist/stages/mux/mux.worker.js.map +1 -0
- package/dist/stages/mux/types.d.ts +95 -0
- package/dist/stages/mux/types.d.ts.map +1 -0
- package/dist/stages/mux/utils.d.ts +32 -0
- package/dist/stages/mux/utils.d.ts.map +1 -0
- package/dist/stages/mux/utils.js +34 -0
- package/dist/stages/mux/utils.js.map +1 -0
- package/dist/types.d.ts +25 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/BackpressureAdapter.d.ts +26 -0
- package/dist/utils/BackpressureAdapter.d.ts.map +1 -0
- package/dist/utils/binary-search.d.ts +33 -0
- package/dist/utils/binary-search.d.ts.map +1 -0
- package/dist/utils/binary-search.js +62 -0
- package/dist/utils/binary-search.js.map +1 -0
- package/dist/utils/canvas-utils.d.ts +96 -0
- package/dist/utils/canvas-utils.d.ts.map +1 -0
- package/dist/utils/canvas-utils.js +58 -0
- package/dist/utils/canvas-utils.js.map +1 -0
- package/dist/utils/object-utils.d.ts +34 -0
- package/dist/utils/object-utils.d.ts.map +1 -0
- package/dist/utils/object-utils.js +22 -0
- package/dist/utils/object-utils.js.map +1 -0
- package/dist/utils/time-utils.d.ts +10 -0
- package/dist/utils/time-utils.d.ts.map +1 -0
- package/dist/utils/time-utils.js +60 -0
- package/dist/utils/time-utils.js.map +1 -0
- package/dist/worker/BaseWorker.d.ts +44 -0
- package/dist/worker/BaseWorker.d.ts.map +1 -0
- package/dist/worker/BaseWorker.js +98 -0
- package/dist/worker/BaseWorker.js.map +1 -0
- package/dist/worker/WorkerChannel.d.ts +105 -0
- package/dist/worker/WorkerChannel.d.ts.map +1 -0
- package/dist/worker/WorkerChannel.js +355 -0
- package/dist/worker/WorkerChannel.js.map +1 -0
- package/dist/worker/WorkerPool.d.ts +52 -0
- package/dist/worker/WorkerPool.d.ts.map +1 -0
- package/dist/worker/WorkerPool.js +124 -0
- package/dist/worker/WorkerPool.js.map +1 -0
- package/dist/worker/index.d.ts +11 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/transferable-helper.d.ts +89 -0
- package/dist/worker/transferable-helper.d.ts.map +1 -0
- package/dist/worker/transferable-helper.js +44 -0
- package/dist/worker/transferable-helper.js.map +1 -0
- package/dist/worker/types.d.ts +179 -0
- package/dist/worker/types.d.ts.map +1 -0
- package/dist/worker/types.js +50 -0
- package/dist/worker/types.js.map +1 -0
- package/dist/worker/worker-event-whitelist.d.ts +23 -0
- package/dist/worker/worker-event-whitelist.d.ts.map +1 -0
- package/dist/worker/worker-retry.d.ts +36 -0
- package/dist/worker/worker-retry.d.ts.map +1 -0
- package/dist/worker/worker-retry.js +55 -0
- package/dist/worker/worker-retry.js.map +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
class FilterProcessor {
|
|
2
|
+
filterCache = /* @__PURE__ */ new Map();
|
|
3
|
+
/**
|
|
4
|
+
* Apply filters to canvas context
|
|
5
|
+
* Combines multiple filters into a single CSS filter string for performance
|
|
6
|
+
*/
|
|
7
|
+
applyFilters(ctx, filters) {
|
|
8
|
+
if (!filters || filters.length === 0) {
|
|
9
|
+
ctx.filter = "none";
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const cacheKey = this.generateCacheKey(filters);
|
|
13
|
+
let filterString = this.filterCache.get(cacheKey);
|
|
14
|
+
if (!filterString) {
|
|
15
|
+
filterString = this.buildFilterString(filters);
|
|
16
|
+
this.filterCache.set(cacheKey, filterString);
|
|
17
|
+
}
|
|
18
|
+
ctx.filter = filterString;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build CSS filter string from filter array
|
|
22
|
+
*/
|
|
23
|
+
buildFilterString(filters) {
|
|
24
|
+
const filterStrings = [];
|
|
25
|
+
for (const filter of filters) {
|
|
26
|
+
const filterStr = this.buildSingleFilter(filter);
|
|
27
|
+
if (filterStr) {
|
|
28
|
+
filterStrings.push(filterStr);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return filterStrings.length > 0 ? filterStrings.join(" ") : "none";
|
|
32
|
+
}
|
|
33
|
+
buildSingleFilter(filter) {
|
|
34
|
+
switch (filter.type) {
|
|
35
|
+
case "blur":
|
|
36
|
+
return `blur(${filter.value ?? 0}px)`;
|
|
37
|
+
case "brightness":
|
|
38
|
+
return `brightness(${filter.value ?? 1})`;
|
|
39
|
+
case "contrast":
|
|
40
|
+
return `contrast(${filter.value ?? 1})`;
|
|
41
|
+
case "grayscale":
|
|
42
|
+
return `grayscale(${filter.value ?? 0})`;
|
|
43
|
+
case "hue-rotate":
|
|
44
|
+
return `hue-rotate(${filter.value ?? 0}deg)`;
|
|
45
|
+
case "saturate":
|
|
46
|
+
return `saturate(${filter.value ?? 1})`;
|
|
47
|
+
case "sepia":
|
|
48
|
+
return `sepia(${filter.value ?? 0})`;
|
|
49
|
+
case "custom":
|
|
50
|
+
return this.buildCustomFilter(filter);
|
|
51
|
+
default:
|
|
52
|
+
console.warn(`Unknown filter type: ${filter.type}`);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Build custom filter from params
|
|
58
|
+
*/
|
|
59
|
+
buildCustomFilter(filter) {
|
|
60
|
+
if (!filter.params) return null;
|
|
61
|
+
const { type, ...params } = filter.params;
|
|
62
|
+
switch (type) {
|
|
63
|
+
case "drop-shadow":
|
|
64
|
+
return `drop-shadow(${params.offsetX}px ${params.offsetY}px ${params.blur}px ${params.color})`;
|
|
65
|
+
case "opacity":
|
|
66
|
+
return `opacity(${params.value})`;
|
|
67
|
+
case "invert":
|
|
68
|
+
return `invert(${params.value})`;
|
|
69
|
+
default:
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Apply color matrix transformation for advanced effects
|
|
75
|
+
* This allows for more complex color manipulations than CSS filters
|
|
76
|
+
*/
|
|
77
|
+
applyColorMatrix(imageData, matrix) {
|
|
78
|
+
if (matrix.length !== 20) {
|
|
79
|
+
throw new Error("Color matrix must have 20 values (4x5 matrix)");
|
|
80
|
+
}
|
|
81
|
+
const data = imageData.data;
|
|
82
|
+
const length = data.length;
|
|
83
|
+
for (let i = 0; i < length; i += 4) {
|
|
84
|
+
const r = data[i];
|
|
85
|
+
const g = data[i + 1];
|
|
86
|
+
const b = data[i + 2];
|
|
87
|
+
const a = data[i + 3];
|
|
88
|
+
const m = matrix;
|
|
89
|
+
data[i] = this.clamp(r * m[0] + g * m[1] + b * m[2] + a * m[3] + m[4] * 255);
|
|
90
|
+
data[i + 1] = this.clamp(r * m[5] + g * m[6] + b * m[7] + a * m[8] + m[9] * 255);
|
|
91
|
+
data[i + 2] = this.clamp(r * m[10] + g * m[11] + b * m[12] + a * m[13] + m[14] * 255);
|
|
92
|
+
data[i + 3] = this.clamp(r * m[15] + g * m[16] + b * m[17] + a * m[18] + m[19] * 255);
|
|
93
|
+
}
|
|
94
|
+
return imageData;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Predefined color matrices for common effects
|
|
98
|
+
*/
|
|
99
|
+
getPresetMatrix(preset) {
|
|
100
|
+
switch (preset) {
|
|
101
|
+
case "vintage":
|
|
102
|
+
return [
|
|
103
|
+
0.393,
|
|
104
|
+
0.769,
|
|
105
|
+
0.189,
|
|
106
|
+
0,
|
|
107
|
+
0,
|
|
108
|
+
0.349,
|
|
109
|
+
0.686,
|
|
110
|
+
0.168,
|
|
111
|
+
0,
|
|
112
|
+
0,
|
|
113
|
+
0.272,
|
|
114
|
+
0.534,
|
|
115
|
+
0.131,
|
|
116
|
+
0,
|
|
117
|
+
0,
|
|
118
|
+
0,
|
|
119
|
+
0,
|
|
120
|
+
0,
|
|
121
|
+
1,
|
|
122
|
+
0
|
|
123
|
+
];
|
|
124
|
+
case "noir":
|
|
125
|
+
return [
|
|
126
|
+
0.25,
|
|
127
|
+
0.25,
|
|
128
|
+
0.25,
|
|
129
|
+
0,
|
|
130
|
+
0,
|
|
131
|
+
0.25,
|
|
132
|
+
0.25,
|
|
133
|
+
0.25,
|
|
134
|
+
0,
|
|
135
|
+
0,
|
|
136
|
+
0.25,
|
|
137
|
+
0.25,
|
|
138
|
+
0.25,
|
|
139
|
+
0,
|
|
140
|
+
0,
|
|
141
|
+
0,
|
|
142
|
+
0,
|
|
143
|
+
0,
|
|
144
|
+
1,
|
|
145
|
+
0
|
|
146
|
+
];
|
|
147
|
+
case "cool":
|
|
148
|
+
return [0.8, 0, 0, 0, 0, 0, 0.9, 0, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1, 0];
|
|
149
|
+
case "warm":
|
|
150
|
+
return [1.2, 0, 0, 0, 0, 0, 1.1, 0, 0, 0, 0, 0, 0.8, 0, 0, 0, 0, 0, 1, 0];
|
|
151
|
+
default:
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Apply Gaussian blur manually (for cases where CSS filter is not enough)
|
|
157
|
+
*/
|
|
158
|
+
applyGaussianBlur(imageData, radius) {
|
|
159
|
+
const output = new ImageData(
|
|
160
|
+
new Uint8ClampedArray(imageData.data),
|
|
161
|
+
imageData.width,
|
|
162
|
+
imageData.height
|
|
163
|
+
);
|
|
164
|
+
const width = imageData.width;
|
|
165
|
+
const height = imageData.height;
|
|
166
|
+
const data = imageData.data;
|
|
167
|
+
const outData = output.data;
|
|
168
|
+
for (let y = 0; y < height; y++) {
|
|
169
|
+
for (let x = 0; x < width; x++) {
|
|
170
|
+
let r = 0, g = 0, b = 0, a = 0;
|
|
171
|
+
let count = 0;
|
|
172
|
+
for (let dx = -radius; dx <= radius; dx++) {
|
|
173
|
+
const nx = Math.min(Math.max(x + dx, 0), width - 1);
|
|
174
|
+
const idx2 = (y * width + nx) * 4;
|
|
175
|
+
r += data[idx2];
|
|
176
|
+
g += data[idx2 + 1];
|
|
177
|
+
b += data[idx2 + 2];
|
|
178
|
+
a += data[idx2 + 3];
|
|
179
|
+
count++;
|
|
180
|
+
}
|
|
181
|
+
const idx = (y * width + x) * 4;
|
|
182
|
+
outData[idx] = r / count;
|
|
183
|
+
outData[idx + 1] = g / count;
|
|
184
|
+
outData[idx + 2] = b / count;
|
|
185
|
+
outData[idx + 3] = a / count;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (let x = 0; x < width; x++) {
|
|
189
|
+
for (let y = 0; y < height; y++) {
|
|
190
|
+
let r = 0, g = 0, b = 0, a = 0;
|
|
191
|
+
let count = 0;
|
|
192
|
+
for (let dy = -radius; dy <= radius; dy++) {
|
|
193
|
+
const ny = Math.min(Math.max(y + dy, 0), height - 1);
|
|
194
|
+
const idx2 = (ny * width + x) * 4;
|
|
195
|
+
r += outData[idx2];
|
|
196
|
+
g += outData[idx2 + 1];
|
|
197
|
+
b += outData[idx2 + 2];
|
|
198
|
+
a += outData[idx2 + 3];
|
|
199
|
+
count++;
|
|
200
|
+
}
|
|
201
|
+
const idx = (y * width + x) * 4;
|
|
202
|
+
data[idx] = r / count;
|
|
203
|
+
data[idx + 1] = g / count;
|
|
204
|
+
data[idx + 2] = b / count;
|
|
205
|
+
data[idx + 3] = a / count;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return imageData;
|
|
209
|
+
}
|
|
210
|
+
clamp(value) {
|
|
211
|
+
return Math.min(255, Math.max(0, Math.round(value)));
|
|
212
|
+
}
|
|
213
|
+
generateCacheKey(filters) {
|
|
214
|
+
return filters.map((f) => `${f.type}:${f.value ?? "default"}`).join("|");
|
|
215
|
+
}
|
|
216
|
+
clearCache() {
|
|
217
|
+
this.filterCache.clear();
|
|
218
|
+
}
|
|
219
|
+
getCacheSize() {
|
|
220
|
+
return this.filterCache.size;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
export {
|
|
224
|
+
FilterProcessor
|
|
225
|
+
};
|
|
226
|
+
//# sourceMappingURL=FilterProcessor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilterProcessor.js","sources":["../../../src/stages/compose/FilterProcessor.ts"],"sourcesContent":["import type { VisualFilter } from './types';\n\n/**\n * FilterProcessor - Handles visual filters and effects\n * Single responsibility: Apply CSS filters and custom shader effects\n */\nexport class FilterProcessor {\n private filterCache = new Map<string, string>();\n\n /**\n * Apply filters to canvas context\n * Combines multiple filters into a single CSS filter string for performance\n */\n applyFilters(ctx: OffscreenCanvasRenderingContext2D, filters: VisualFilter[]): void {\n if (!filters || filters.length === 0) {\n ctx.filter = 'none';\n return;\n }\n\n // Generate cache key\n const cacheKey = this.generateCacheKey(filters);\n\n // Check cache\n let filterString = this.filterCache.get(cacheKey);\n\n if (!filterString) {\n filterString = this.buildFilterString(filters);\n this.filterCache.set(cacheKey, filterString);\n }\n\n ctx.filter = filterString;\n }\n\n /**\n * Build CSS filter string from filter array\n */\n private buildFilterString(filters: VisualFilter[]): string {\n const filterStrings: string[] = [];\n\n for (const filter of filters) {\n const filterStr = this.buildSingleFilter(filter);\n if (filterStr) {\n filterStrings.push(filterStr);\n }\n }\n\n return filterStrings.length > 0 ? filterStrings.join(' ') : 'none';\n }\n\n private buildSingleFilter(filter: VisualFilter): string | null {\n switch (filter.type) {\n case 'blur':\n return `blur(${filter.value ?? 0}px)`;\n\n case 'brightness':\n return `brightness(${filter.value ?? 1})`;\n\n case 'contrast':\n return `contrast(${filter.value ?? 1})`;\n\n case 'grayscale':\n return `grayscale(${filter.value ?? 0})`;\n\n case 'hue-rotate':\n return `hue-rotate(${filter.value ?? 0}deg)`;\n\n case 'saturate':\n return `saturate(${filter.value ?? 1})`;\n\n case 'sepia':\n return `sepia(${filter.value ?? 0})`;\n\n case 'custom':\n return this.buildCustomFilter(filter);\n\n default:\n console.warn(`Unknown filter type: ${filter.type}`);\n return null;\n }\n }\n\n /**\n * Build custom filter from params\n */\n private buildCustomFilter(filter: VisualFilter): string | null {\n if (!filter.params) return null;\n\n const { type, ...params } = filter.params;\n\n switch (type) {\n case 'drop-shadow':\n return `drop-shadow(${params.offsetX}px ${params.offsetY}px ${params.blur}px ${params.color})`;\n\n case 'opacity':\n return `opacity(${params.value})`;\n\n case 'invert':\n return `invert(${params.value})`;\n\n default:\n return null;\n }\n }\n\n /**\n * Apply color matrix transformation for advanced effects\n * This allows for more complex color manipulations than CSS filters\n */\n applyColorMatrix(imageData: ImageData, matrix: number[]): ImageData {\n if (matrix.length !== 20) {\n throw new Error('Color matrix must have 20 values (4x5 matrix)');\n }\n\n const data = imageData.data;\n const length = data.length;\n\n for (let i = 0; i < length; i += 4) {\n const r = data[i]!;\n const g = data[i + 1]!;\n const b = data[i + 2]!;\n const a = data[i + 3]!;\n const m = matrix;\n\n // Apply matrix transformation\n data[i] = this.clamp(r * m[0]! + g * m[1]! + b * m[2]! + a * m[3]! + m[4]! * 255);\n data[i + 1] = this.clamp(r * m[5]! + g * m[6]! + b * m[7]! + a * m[8]! + m[9]! * 255);\n data[i + 2] = this.clamp(r * m[10]! + g * m[11]! + b * m[12]! + a * m[13]! + m[14]! * 255);\n data[i + 3] = this.clamp(r * m[15]! + g * m[16]! + b * m[17]! + a * m[18]! + m[19]! * 255);\n }\n\n return imageData;\n }\n\n /**\n * Predefined color matrices for common effects\n */\n getPresetMatrix(preset: string): number[] | null {\n switch (preset) {\n case 'vintage':\n return [\n 0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, 0.272, 0.534, 0.131, 0, 0, 0, 0, 0,\n 1, 0,\n ];\n\n case 'noir':\n return [\n 0.25, 0.25, 0.25, 0, 0, 0.25, 0.25, 0.25, 0, 0, 0.25, 0.25, 0.25, 0, 0, 0, 0, 0, 1, 0,\n ];\n\n case 'cool':\n return [0.8, 0, 0, 0, 0, 0, 0.9, 0, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1, 0];\n\n case 'warm':\n return [1.2, 0, 0, 0, 0, 0, 1.1, 0, 0, 0, 0, 0, 0.8, 0, 0, 0, 0, 0, 1, 0];\n\n default:\n return null;\n }\n }\n\n /**\n * Apply Gaussian blur manually (for cases where CSS filter is not enough)\n */\n applyGaussianBlur(imageData: ImageData, radius: number): ImageData {\n // Simplified box blur approximation of Gaussian blur\n const output = new ImageData(\n new Uint8ClampedArray(imageData.data),\n imageData.width,\n imageData.height\n );\n\n const width = imageData.width;\n const height = imageData.height;\n const data = imageData.data;\n const outData = output.data;\n\n // Horizontal pass\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n let r = 0,\n g = 0,\n b = 0,\n a = 0;\n let count = 0;\n\n for (let dx = -radius; dx <= radius; dx++) {\n const nx = Math.min(Math.max(x + dx, 0), width - 1);\n const idx = (y * width + nx) * 4;\n r += data[idx]!;\n g += data[idx + 1]!;\n b += data[idx + 2]!;\n a += data[idx + 3]!;\n count++;\n }\n\n const idx = (y * width + x) * 4;\n outData[idx] = r / count;\n outData[idx + 1] = g / count;\n outData[idx + 2] = b / count;\n outData[idx + 3] = a / count;\n }\n }\n\n // Vertical pass\n for (let x = 0; x < width; x++) {\n for (let y = 0; y < height; y++) {\n let r = 0,\n g = 0,\n b = 0,\n a = 0;\n let count = 0;\n\n for (let dy = -radius; dy <= radius; dy++) {\n const ny = Math.min(Math.max(y + dy, 0), height - 1);\n const idx = (ny * width + x) * 4;\n r += outData[idx]!;\n g += outData[idx + 1]!;\n b += outData[idx + 2]!;\n a += outData[idx + 3]!;\n count++;\n }\n\n const idx = (y * width + x) * 4;\n data[idx] = r / count;\n data[idx + 1] = g / count;\n data[idx + 2] = b / count;\n data[idx + 3] = a / count;\n }\n }\n\n return imageData;\n }\n\n private clamp(value: number): number {\n return Math.min(255, Math.max(0, Math.round(value)));\n }\n\n private generateCacheKey(filters: VisualFilter[]): string {\n return filters.map((f) => `${f.type}:${f.value ?? 'default'}`).join('|');\n }\n\n clearCache(): void {\n this.filterCache.clear();\n }\n\n getCacheSize(): number {\n return this.filterCache.size;\n }\n}\n"],"names":["idx"],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { TimeUs } from '../../model/types';
|
|
2
|
+
import { CompositionModel } from '../../model';
|
|
3
|
+
import { WorkerPool } from '../../worker/WorkerPool';
|
|
4
|
+
import { ResourceLoader } from '../load/ResourceLoader';
|
|
5
|
+
import { EventBus } from '../../event/EventBus';
|
|
6
|
+
import { EventPayloadMap } from '../../event/events';
|
|
7
|
+
import { CacheManager } from '../../cache/CacheManager';
|
|
8
|
+
|
|
9
|
+
interface AudioDataMessage {
|
|
10
|
+
clipId: string;
|
|
11
|
+
audioData: AudioData;
|
|
12
|
+
clipStartUs: TimeUs;
|
|
13
|
+
clipDurationUs: TimeUs;
|
|
14
|
+
}
|
|
15
|
+
interface AudioSessionDeps {
|
|
16
|
+
cacheManager: CacheManager;
|
|
17
|
+
workers: WorkerPool;
|
|
18
|
+
resourceLoader: ResourceLoader;
|
|
19
|
+
eventBus: EventBus<EventPayloadMap>;
|
|
20
|
+
getModel: () => CompositionModel | null;
|
|
21
|
+
buildWorkerConfigs: () => any;
|
|
22
|
+
}
|
|
23
|
+
export declare class GlobalAudioSession {
|
|
24
|
+
private mixWindowUs;
|
|
25
|
+
private mixer;
|
|
26
|
+
private activeClips;
|
|
27
|
+
private deps;
|
|
28
|
+
constructor(deps: AudioSessionDeps);
|
|
29
|
+
onAudioData(message: AudioDataMessage): void;
|
|
30
|
+
ensureMixedPCM(startUs: TimeUs): Promise<AudioBuffer | null>;
|
|
31
|
+
activateAllAudioClips(): Promise<void>;
|
|
32
|
+
handleAudioStream(stream: ReadableStream<AudioData>, metadata: Record<string, any>): void;
|
|
33
|
+
reset(): void;
|
|
34
|
+
private setupAudioPipeline;
|
|
35
|
+
private alignToWindow;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=GlobalAudioSession.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GlobalAudioSession.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/GlobalAudioSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,gBAAgB,EAAQ,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,gBAAgB;IACxB,YAAY,EAAE,YAAY,CAAC;IAC3B,OAAO,EAAE,UAAU,CAAC;IACpB,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACpC,QAAQ,EAAE,MAAM,gBAAgB,GAAG,IAAI,CAAC;IACxC,kBAAkB,EAAE,MAAM,GAAG,CAAC;CAC/B;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,IAAI,CAAmB;gBAEnB,IAAI,EAAE,gBAAgB;IAKlC,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAKtC,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAwB5D,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB5C,iBAAiB,CAAC,MAAM,EAAE,cAAc,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IA+BzF,KAAK,IAAI,IAAI;YAKC,kBAAkB;IAiChC,OAAO,CAAC,aAAa;CAGtB"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { OfflineAudioMixer } from "./OfflineAudioMixer.js";
|
|
2
|
+
import { MeframeEvent } from "../../event/events.js";
|
|
3
|
+
class GlobalAudioSession {
|
|
4
|
+
mixWindowUs = 3e6;
|
|
5
|
+
mixer;
|
|
6
|
+
activeClips = /* @__PURE__ */ new Set();
|
|
7
|
+
deps;
|
|
8
|
+
constructor(deps) {
|
|
9
|
+
this.deps = deps;
|
|
10
|
+
this.mixer = new OfflineAudioMixer(deps.cacheManager, deps.getModel);
|
|
11
|
+
}
|
|
12
|
+
onAudioData(message) {
|
|
13
|
+
const { clipId, audioData, clipStartUs, clipDurationUs } = message;
|
|
14
|
+
this.deps.cacheManager.putClipAudioData(clipId, audioData, clipStartUs, clipDurationUs);
|
|
15
|
+
}
|
|
16
|
+
async ensureMixedPCM(startUs) {
|
|
17
|
+
const model = this.deps.getModel();
|
|
18
|
+
if (!model) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const windowStartUs = this.alignToWindow(startUs);
|
|
22
|
+
const windowEndUs = windowStartUs + this.mixWindowUs;
|
|
23
|
+
const cached = this.deps.cacheManager.getMixedAudio(windowStartUs, windowEndUs);
|
|
24
|
+
if (cached) {
|
|
25
|
+
return cached;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const mixedBuffer = await this.mixer.mix(windowStartUs, windowEndUs);
|
|
29
|
+
this.deps.cacheManager.putMixedAudio(windowStartUs, windowEndUs, mixedBuffer);
|
|
30
|
+
return mixedBuffer;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error("[GlobalAudioSession] Mix failed:", error);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async activateAllAudioClips() {
|
|
37
|
+
const model = this.deps.getModel();
|
|
38
|
+
if (!model) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const audioTracks = model.tracks.filter((track) => track.kind === "audio");
|
|
42
|
+
for (const track of audioTracks) {
|
|
43
|
+
for (const clip of track.clips) {
|
|
44
|
+
if (!this.activeClips.has(clip.id)) {
|
|
45
|
+
await this.setupAudioPipeline(clip);
|
|
46
|
+
this.activeClips.add(clip.id);
|
|
47
|
+
await this.deps.resourceLoader.fetch(clip.resourceId, {
|
|
48
|
+
priority: "high"
|
|
49
|
+
});
|
|
50
|
+
this.deps.eventBus.emit(MeframeEvent.ClipActivated, { clipId: clip.id });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
handleAudioStream(stream, metadata) {
|
|
56
|
+
const clipId = metadata.clipId || "unknown";
|
|
57
|
+
const clipStartUs = metadata.clipStartUs ?? 0;
|
|
58
|
+
const clipDurationUs = metadata.clipDurationUs ?? 0;
|
|
59
|
+
const reader = stream.getReader();
|
|
60
|
+
const pump = async () => {
|
|
61
|
+
try {
|
|
62
|
+
const { done, value } = await reader.read();
|
|
63
|
+
if (done) {
|
|
64
|
+
reader.releaseLock();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.onAudioData({
|
|
68
|
+
clipId,
|
|
69
|
+
audioData: value,
|
|
70
|
+
clipStartUs,
|
|
71
|
+
clipDurationUs
|
|
72
|
+
});
|
|
73
|
+
await pump();
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error("[GlobalAudioSession] Audio stream error:", error);
|
|
76
|
+
reader.releaseLock();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
pump();
|
|
80
|
+
}
|
|
81
|
+
reset() {
|
|
82
|
+
this.deps.cacheManager.resetAudioCache();
|
|
83
|
+
this.activeClips.clear();
|
|
84
|
+
}
|
|
85
|
+
async setupAudioPipeline(clip) {
|
|
86
|
+
const { id: clipId, resourceId, startUs, durationUs } = clip;
|
|
87
|
+
const audioDemuxWorker = await this.deps.workers.get("audioDemux", resourceId, { lazy: true });
|
|
88
|
+
const decodeWorker = await this.deps.workers.get("decode");
|
|
89
|
+
const demuxToDecodeChannel = new MessageChannel();
|
|
90
|
+
await audioDemuxWorker.send(
|
|
91
|
+
"connect",
|
|
92
|
+
{ direction: "downstream", port: demuxToDecodeChannel.port1, streamType: "audio", clipId },
|
|
93
|
+
{ transfer: [demuxToDecodeChannel.port1] }
|
|
94
|
+
);
|
|
95
|
+
await decodeWorker.send(
|
|
96
|
+
"connect",
|
|
97
|
+
{
|
|
98
|
+
direction: "upstream",
|
|
99
|
+
port: demuxToDecodeChannel.port2,
|
|
100
|
+
streamType: "audio",
|
|
101
|
+
clipId,
|
|
102
|
+
clipStartUs: startUs || 0,
|
|
103
|
+
clipDurationUs: durationUs || 0
|
|
104
|
+
},
|
|
105
|
+
{ transfer: [demuxToDecodeChannel.port2] }
|
|
106
|
+
);
|
|
107
|
+
const demuxConfig = this.deps.buildWorkerConfigs().audioDemux;
|
|
108
|
+
await audioDemuxWorker.send("configure", {
|
|
109
|
+
initial: true,
|
|
110
|
+
resourceId,
|
|
111
|
+
clipId,
|
|
112
|
+
config: demuxConfig
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
alignToWindow(timeUs) {
|
|
116
|
+
return Math.floor(timeUs / this.mixWindowUs) * this.mixWindowUs;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export {
|
|
120
|
+
GlobalAudioSession
|
|
121
|
+
};
|
|
122
|
+
//# sourceMappingURL=GlobalAudioSession.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GlobalAudioSession.js","sources":["../../../src/stages/compose/GlobalAudioSession.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport { OfflineAudioMixer } from './OfflineAudioMixer';\nimport type { CompositionModel, Clip } from '../../model';\nimport type { WorkerPool } from '../../worker/WorkerPool';\nimport type { ResourceLoader } from '../load/ResourceLoader';\nimport type { EventBus } from '../../event/EventBus';\nimport type { EventPayloadMap } from '../../event/events';\nimport { MeframeEvent } from '../../event/events';\nimport type { CacheManager } from '../../cache/CacheManager';\n\ninterface AudioDataMessage {\n clipId: string;\n audioData: AudioData;\n clipStartUs: TimeUs;\n clipDurationUs: TimeUs;\n}\n\ninterface AudioSessionDeps {\n cacheManager: CacheManager;\n workers: WorkerPool;\n resourceLoader: ResourceLoader;\n eventBus: EventBus<EventPayloadMap>;\n getModel: () => CompositionModel | null;\n buildWorkerConfigs: () => any;\n}\n\nexport class GlobalAudioSession {\n private mixWindowUs = 3_000_000;\n private mixer: OfflineAudioMixer;\n private activeClips = new Set<string>();\n private deps: AudioSessionDeps;\n\n constructor(deps: AudioSessionDeps) {\n this.deps = deps;\n this.mixer = new OfflineAudioMixer(deps.cacheManager, deps.getModel);\n }\n\n onAudioData(message: AudioDataMessage): void {\n const { clipId, audioData, clipStartUs, clipDurationUs } = message;\n this.deps.cacheManager.putClipAudioData(clipId, audioData, clipStartUs, clipDurationUs);\n }\n\n async ensureMixedPCM(startUs: TimeUs): Promise<AudioBuffer | null> {\n const model = this.deps.getModel();\n if (!model) {\n return null;\n }\n\n const windowStartUs = this.alignToWindow(startUs);\n const windowEndUs = windowStartUs + this.mixWindowUs;\n\n const cached = this.deps.cacheManager.getMixedAudio(windowStartUs, windowEndUs);\n if (cached) {\n return cached;\n }\n\n try {\n const mixedBuffer = await this.mixer.mix(windowStartUs, windowEndUs);\n this.deps.cacheManager.putMixedAudio(windowStartUs, windowEndUs, mixedBuffer);\n return mixedBuffer;\n } catch (error) {\n console.error('[GlobalAudioSession] Mix failed:', error);\n return null;\n }\n }\n\n async activateAllAudioClips(): Promise<void> {\n const model = this.deps.getModel();\n if (!model) {\n return;\n }\n\n const audioTracks = model.tracks.filter((track) => track.kind === 'audio');\n\n for (const track of audioTracks) {\n for (const clip of track.clips) {\n if (!this.activeClips.has(clip.id)) {\n await this.setupAudioPipeline(clip);\n this.activeClips.add(clip.id);\n\n await this.deps.resourceLoader.fetch(clip.resourceId, {\n priority: 'high',\n });\n\n this.deps.eventBus.emit(MeframeEvent.ClipActivated, { clipId: clip.id });\n }\n }\n }\n }\n\n handleAudioStream(stream: ReadableStream<AudioData>, metadata: Record<string, any>): void {\n const clipId = metadata.clipId || 'unknown';\n const clipStartUs = metadata.clipStartUs ?? 0;\n const clipDurationUs = metadata.clipDurationUs ?? 0;\n\n const reader = stream.getReader();\n const pump = async (): Promise<void> => {\n try {\n const { done, value } = await reader.read();\n if (done) {\n reader.releaseLock();\n return;\n }\n\n this.onAudioData({\n clipId,\n audioData: value,\n clipStartUs,\n clipDurationUs,\n });\n\n await pump();\n } catch (error) {\n console.error('[GlobalAudioSession] Audio stream error:', error);\n reader.releaseLock();\n }\n };\n\n pump();\n }\n\n reset(): void {\n this.deps.cacheManager.resetAudioCache();\n this.activeClips.clear();\n }\n\n private async setupAudioPipeline(clip: Clip): Promise<void> {\n const { id: clipId, resourceId, startUs, durationUs } = clip;\n const audioDemuxWorker = await this.deps.workers.get('audioDemux', resourceId, { lazy: true });\n const decodeWorker = await this.deps.workers.get('decode');\n\n const demuxToDecodeChannel = new MessageChannel();\n await audioDemuxWorker.send(\n 'connect',\n { direction: 'downstream', port: demuxToDecodeChannel.port1, streamType: 'audio', clipId },\n { transfer: [demuxToDecodeChannel.port1] }\n );\n await decodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: demuxToDecodeChannel.port2,\n streamType: 'audio',\n clipId,\n clipStartUs: startUs || 0,\n clipDurationUs: durationUs || 0,\n },\n { transfer: [demuxToDecodeChannel.port2] }\n );\n\n const demuxConfig = this.deps.buildWorkerConfigs().audioDemux;\n await audioDemuxWorker.send('configure', {\n initial: true,\n resourceId,\n clipId,\n config: demuxConfig,\n });\n }\n\n private alignToWindow(timeUs: TimeUs): TimeUs {\n return Math.floor(timeUs / this.mixWindowUs) * this.mixWindowUs;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Layer } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LayerRenderer - Handles rendering of individual layers
|
|
5
|
+
* Single responsibility: Draw a single layer to the canvas context
|
|
6
|
+
*/
|
|
7
|
+
export declare class LayerRenderer {
|
|
8
|
+
private ctx;
|
|
9
|
+
private width;
|
|
10
|
+
private height;
|
|
11
|
+
constructor(ctx: OffscreenCanvasRenderingContext2D, width: number, height: number);
|
|
12
|
+
private ensureHighQualityRendering;
|
|
13
|
+
/**
|
|
14
|
+
* Render a single layer with all its properties
|
|
15
|
+
*/
|
|
16
|
+
renderLayer(layer: Layer): Promise<void>;
|
|
17
|
+
private applyTransform;
|
|
18
|
+
private renderVideoLayer;
|
|
19
|
+
private renderImageLayer;
|
|
20
|
+
private renderTextLayer;
|
|
21
|
+
/**
|
|
22
|
+
* Draw enhanced multi-layer stroke for better text visibility
|
|
23
|
+
*/
|
|
24
|
+
private drawEnhancedStroke;
|
|
25
|
+
private calculateTextX;
|
|
26
|
+
private calculateTextY;
|
|
27
|
+
private applyMask;
|
|
28
|
+
updateDimensions(width: number, height: number): void;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=LayerRenderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LayerRenderer.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/LayerRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAA8D,MAAM,SAAS,CAAC;AAEjG;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,GAAG,CAAoC;IAC/C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;gBAEX,GAAG,EAAE,iCAAiC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAOjF,OAAO,CAAC,0BAA0B;IAKlC;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAyC9C,OAAO,CAAC,cAAc;YAmBR,gBAAgB;YAuChB,gBAAgB;YAsChB,eAAe;IAkD7B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAwB1B,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,SAAS;IAkBjB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAKtD"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
class LayerRenderer {
|
|
2
|
+
ctx;
|
|
3
|
+
width;
|
|
4
|
+
height;
|
|
5
|
+
constructor(ctx, width, height) {
|
|
6
|
+
this.ctx = ctx;
|
|
7
|
+
this.width = width;
|
|
8
|
+
this.height = height;
|
|
9
|
+
this.ensureHighQualityRendering();
|
|
10
|
+
}
|
|
11
|
+
ensureHighQualityRendering() {
|
|
12
|
+
this.ctx.imageSmoothingEnabled = true;
|
|
13
|
+
this.ctx.imageSmoothingQuality = "high";
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Render a single layer with all its properties
|
|
17
|
+
*/
|
|
18
|
+
async renderLayer(layer) {
|
|
19
|
+
if (!layer.visible || layer.opacity <= 0) return;
|
|
20
|
+
this.ctx.save();
|
|
21
|
+
try {
|
|
22
|
+
this.ensureHighQualityRendering();
|
|
23
|
+
this.ctx.globalAlpha = layer.opacity;
|
|
24
|
+
if (layer.blendMode) {
|
|
25
|
+
this.ctx.globalCompositeOperation = layer.blendMode;
|
|
26
|
+
}
|
|
27
|
+
if (layer.transform) {
|
|
28
|
+
this.applyTransform(layer.transform);
|
|
29
|
+
}
|
|
30
|
+
switch (layer.type) {
|
|
31
|
+
case "video":
|
|
32
|
+
await this.renderVideoLayer(layer);
|
|
33
|
+
break;
|
|
34
|
+
case "image":
|
|
35
|
+
await this.renderImageLayer(layer);
|
|
36
|
+
break;
|
|
37
|
+
case "text":
|
|
38
|
+
await this.renderTextLayer(layer);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
if (layer.mask) {
|
|
42
|
+
this.applyMask(layer.mask);
|
|
43
|
+
}
|
|
44
|
+
} finally {
|
|
45
|
+
this.ctx.restore();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
applyTransform(transform) {
|
|
49
|
+
const centerX = this.width * (transform.anchorX ?? 0.5);
|
|
50
|
+
const centerY = this.height * (transform.anchorY ?? 0.5);
|
|
51
|
+
this.ctx.translate(transform.x + centerX, transform.y + centerY);
|
|
52
|
+
if (transform.rotation) {
|
|
53
|
+
this.ctx.rotate(transform.rotation);
|
|
54
|
+
}
|
|
55
|
+
this.ctx.scale(transform.scaleX, transform.scaleY);
|
|
56
|
+
if (transform.skewX || transform.skewY) {
|
|
57
|
+
this.ctx.transform(1, transform.skewY ?? 0, transform.skewX ?? 0, 1, 0, 0);
|
|
58
|
+
}
|
|
59
|
+
this.ctx.translate(-centerX, -centerY);
|
|
60
|
+
}
|
|
61
|
+
async renderVideoLayer(layer) {
|
|
62
|
+
const { videoFrame, crop } = layer;
|
|
63
|
+
const videoWidth = videoFrame.displayWidth || videoFrame.codedWidth;
|
|
64
|
+
const videoHeight = videoFrame.displayHeight || videoFrame.codedHeight;
|
|
65
|
+
const scaleX = this.width / videoWidth;
|
|
66
|
+
const scaleY = this.height / videoHeight;
|
|
67
|
+
const scale = Math.min(scaleX, scaleY);
|
|
68
|
+
const renderWidth = Math.round(videoWidth * scale);
|
|
69
|
+
const renderHeight = Math.round(videoHeight * scale);
|
|
70
|
+
const renderX = Math.round((this.width - renderWidth) / 2);
|
|
71
|
+
const renderY = Math.round((this.height - renderHeight) / 2);
|
|
72
|
+
if (crop) {
|
|
73
|
+
this.ctx.drawImage(
|
|
74
|
+
videoFrame,
|
|
75
|
+
crop.x,
|
|
76
|
+
crop.y,
|
|
77
|
+
crop.width,
|
|
78
|
+
crop.height,
|
|
79
|
+
renderX,
|
|
80
|
+
renderY,
|
|
81
|
+
renderWidth,
|
|
82
|
+
renderHeight
|
|
83
|
+
);
|
|
84
|
+
} else {
|
|
85
|
+
this.ctx.drawImage(videoFrame, renderX, renderY, renderWidth, renderHeight);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async renderImageLayer(layer) {
|
|
89
|
+
const { source, crop } = layer;
|
|
90
|
+
if (source instanceof ImageData) {
|
|
91
|
+
if (crop) {
|
|
92
|
+
const tempCanvas = new OffscreenCanvas(crop.width, crop.height);
|
|
93
|
+
const tempCtx = tempCanvas.getContext("2d");
|
|
94
|
+
tempCtx.putImageData(source, -crop.x, -crop.y);
|
|
95
|
+
this.ctx.drawImage(tempCanvas, 0, 0, this.width, this.height);
|
|
96
|
+
} else {
|
|
97
|
+
this.ctx.putImageData(source, 0, 0);
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
if (!source) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (crop) {
|
|
104
|
+
this.ctx.drawImage(
|
|
105
|
+
source,
|
|
106
|
+
crop.x,
|
|
107
|
+
crop.y,
|
|
108
|
+
crop.width,
|
|
109
|
+
crop.height,
|
|
110
|
+
0,
|
|
111
|
+
0,
|
|
112
|
+
this.width,
|
|
113
|
+
this.height
|
|
114
|
+
);
|
|
115
|
+
} else {
|
|
116
|
+
this.ctx.drawImage(source, 0, 0, this.width, this.height);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async renderTextLayer(layer) {
|
|
121
|
+
const fontSize = layer.fontSize ?? 16;
|
|
122
|
+
const fontFamily = layer.fontFamily ?? "sans-serif";
|
|
123
|
+
const fontWeight = layer.fontWeight ?? "normal";
|
|
124
|
+
const fontStyle = layer.fontStyle ?? "normal";
|
|
125
|
+
this.ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
126
|
+
this.ctx.fillStyle = layer.color ?? "#000000";
|
|
127
|
+
this.ctx.textAlign = layer.textAlign ?? "left";
|
|
128
|
+
this.ctx.textBaseline = layer.verticalAlign ?? "top";
|
|
129
|
+
if (layer.letterSpacing && typeof this.ctx.letterSpacing !== "undefined") {
|
|
130
|
+
this.ctx.letterSpacing = `${layer.letterSpacing}px`;
|
|
131
|
+
}
|
|
132
|
+
this.ensureHighQualityRendering();
|
|
133
|
+
const baseX = this.calculateTextX(layer.textAlign);
|
|
134
|
+
const baseY = this.calculateTextY(layer.verticalAlign, fontSize);
|
|
135
|
+
const x = Math.round(baseX) + 0.5;
|
|
136
|
+
const y = Math.round(baseY) + 0.5;
|
|
137
|
+
if (layer.shadow) {
|
|
138
|
+
this.ctx.shadowColor = layer.shadow.color;
|
|
139
|
+
this.ctx.shadowOffsetX = layer.shadow.offsetX;
|
|
140
|
+
this.ctx.shadowOffsetY = layer.shadow.offsetY;
|
|
141
|
+
this.ctx.shadowBlur = layer.shadow.blur;
|
|
142
|
+
}
|
|
143
|
+
if (layer.strokeColor && layer.strokeWidth && layer.strokeWidth > 0) {
|
|
144
|
+
this.drawEnhancedStroke(layer.text, x, y, layer.strokeColor, layer.strokeWidth);
|
|
145
|
+
}
|
|
146
|
+
this.ctx.fillText(layer.text, x, y);
|
|
147
|
+
if (layer.shadow) {
|
|
148
|
+
this.ctx.shadowColor = "transparent";
|
|
149
|
+
this.ctx.shadowOffsetX = 0;
|
|
150
|
+
this.ctx.shadowOffsetY = 0;
|
|
151
|
+
this.ctx.shadowBlur = 0;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Draw enhanced multi-layer stroke for better text visibility
|
|
156
|
+
*/
|
|
157
|
+
drawEnhancedStroke(text, x, y, strokeColor, strokeWidth) {
|
|
158
|
+
this.ctx.save();
|
|
159
|
+
this.ctx.strokeStyle = strokeColor;
|
|
160
|
+
this.ctx.lineJoin = "round";
|
|
161
|
+
this.ctx.lineCap = "round";
|
|
162
|
+
this.ctx.miterLimit = 2;
|
|
163
|
+
const layers = [1.1, 1];
|
|
164
|
+
layers.forEach((multiplier) => {
|
|
165
|
+
this.ctx.lineWidth = strokeWidth * multiplier;
|
|
166
|
+
this.ctx.strokeText(text, x, y);
|
|
167
|
+
});
|
|
168
|
+
this.ctx.restore();
|
|
169
|
+
}
|
|
170
|
+
calculateTextX(align) {
|
|
171
|
+
switch (align) {
|
|
172
|
+
case "center":
|
|
173
|
+
return this.width / 2;
|
|
174
|
+
case "right":
|
|
175
|
+
return this.width;
|
|
176
|
+
default:
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
calculateTextY(align, fontSize = 16) {
|
|
181
|
+
switch (align) {
|
|
182
|
+
case "middle":
|
|
183
|
+
return this.height / 2;
|
|
184
|
+
case "bottom":
|
|
185
|
+
return this.height * 0.85;
|
|
186
|
+
default:
|
|
187
|
+
return fontSize;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
applyMask(mask) {
|
|
191
|
+
this.ctx.globalCompositeOperation = mask.invert ? "source-out" : "destination-in";
|
|
192
|
+
if (mask.source) {
|
|
193
|
+
this.ctx.drawImage(mask.source, 0, 0, this.width, this.height);
|
|
194
|
+
} else if (mask.shape === "circle") {
|
|
195
|
+
this.ctx.beginPath();
|
|
196
|
+
this.ctx.arc(
|
|
197
|
+
this.width / 2,
|
|
198
|
+
this.height / 2,
|
|
199
|
+
Math.min(this.width, this.height) / 2,
|
|
200
|
+
0,
|
|
201
|
+
Math.PI * 2
|
|
202
|
+
);
|
|
203
|
+
this.ctx.fill();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
updateDimensions(width, height) {
|
|
207
|
+
this.width = width;
|
|
208
|
+
this.height = height;
|
|
209
|
+
this.ensureHighQualityRendering();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
export {
|
|
213
|
+
LayerRenderer
|
|
214
|
+
};
|
|
215
|
+
//# sourceMappingURL=LayerRenderer.js.map
|