@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,25 +0,0 @@
|
|
|
1
|
-
import { PreviewHandle, TimeUs } from './types';
|
|
2
|
-
import { PlaybackController } from './PlaybackController';
|
|
3
|
-
import { EventBus } from '../event/EventBus';
|
|
4
|
-
import { EventPayloadMap } from '../event/events';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* PreviewHandle implementation for playback control
|
|
8
|
-
*/
|
|
9
|
-
export declare class PreviewHandleImpl implements PreviewHandle {
|
|
10
|
-
private controller;
|
|
11
|
-
private eventBus;
|
|
12
|
-
constructor(controller: PlaybackController, eventBus: EventBus<EventPayloadMap>);
|
|
13
|
-
play(): void;
|
|
14
|
-
seek(timeUs: TimeUs): void;
|
|
15
|
-
setRate(rate: number): void;
|
|
16
|
-
pause(): void;
|
|
17
|
-
resume(): void;
|
|
18
|
-
stop(): void;
|
|
19
|
-
setVolume(volume: number): void;
|
|
20
|
-
get currentTimeUs(): TimeUs;
|
|
21
|
-
get isPlaying(): boolean;
|
|
22
|
-
on(event: string, handler: Function): void;
|
|
23
|
-
off(event: string, handler: Function): void;
|
|
24
|
-
}
|
|
25
|
-
//# sourceMappingURL=PreviewHandle.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PreviewHandle.d.ts","sourceRoot":"","sources":["../../src/controllers/PreviewHandle.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD;;GAEG;AACH,qBAAa,iBAAkB,YAAW,aAAa;IACrD,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,QAAQ,CAA4B;gBAEhC,UAAU,EAAE,kBAAkB,EAAE,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC;IAK/E,IAAI,IAAI,IAAI;IAIZ,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI1B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI3B,KAAK,IAAI,IAAI;IAIb,MAAM,IAAI,IAAI;IAId,IAAI,IAAI,IAAI;IAIZ,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI;IAI1C,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI;CAG5C"}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
class PreviewHandleImpl {
|
|
2
|
-
controller;
|
|
3
|
-
eventBus;
|
|
4
|
-
constructor(controller, eventBus) {
|
|
5
|
-
this.controller = controller;
|
|
6
|
-
this.eventBus = eventBus;
|
|
7
|
-
}
|
|
8
|
-
play() {
|
|
9
|
-
this.controller.play();
|
|
10
|
-
}
|
|
11
|
-
seek(timeUs) {
|
|
12
|
-
this.controller.seek(timeUs);
|
|
13
|
-
}
|
|
14
|
-
setRate(rate) {
|
|
15
|
-
this.controller.setRate(rate);
|
|
16
|
-
}
|
|
17
|
-
pause() {
|
|
18
|
-
this.controller.pause();
|
|
19
|
-
}
|
|
20
|
-
resume() {
|
|
21
|
-
this.controller.resume();
|
|
22
|
-
}
|
|
23
|
-
stop() {
|
|
24
|
-
this.controller.stop();
|
|
25
|
-
}
|
|
26
|
-
setVolume(volume) {
|
|
27
|
-
this.controller.setVolume(volume);
|
|
28
|
-
}
|
|
29
|
-
get currentTimeUs() {
|
|
30
|
-
return this.controller.currentTime;
|
|
31
|
-
}
|
|
32
|
-
get isPlaying() {
|
|
33
|
-
return this.controller.playing;
|
|
34
|
-
}
|
|
35
|
-
on(event, handler) {
|
|
36
|
-
this.eventBus.on(event, handler);
|
|
37
|
-
}
|
|
38
|
-
off(event, handler) {
|
|
39
|
-
this.eventBus.off(event, handler);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
export {
|
|
43
|
-
PreviewHandleImpl
|
|
44
|
-
};
|
|
45
|
-
//# sourceMappingURL=PreviewHandle.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PreviewHandle.js","sources":["../../src/controllers/PreviewHandle.ts"],"sourcesContent":["/**\n * PreviewHandle: Implementation of preview playback control handle\n */\n\nimport type { PreviewHandle, TimeUs } from './types';\nimport type { PlaybackController } from './PlaybackController';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\n\n/**\n * PreviewHandle implementation for playback control\n */\nexport class PreviewHandleImpl implements PreviewHandle {\n private controller: PlaybackController;\n private eventBus: EventBus<EventPayloadMap>;\n\n constructor(controller: PlaybackController, eventBus: EventBus<EventPayloadMap>) {\n this.controller = controller;\n this.eventBus = eventBus;\n }\n\n play(): void {\n this.controller.play();\n }\n\n seek(timeUs: TimeUs): void {\n this.controller.seek(timeUs);\n }\n\n setRate(rate: number): void {\n this.controller.setRate(rate);\n }\n\n pause(): void {\n this.controller.pause();\n }\n\n resume(): void {\n this.controller.resume();\n }\n\n stop(): void {\n this.controller.stop();\n }\n\n setVolume(volume: number): void {\n this.controller.setVolume(volume);\n }\n\n get currentTimeUs(): TimeUs {\n return this.controller.currentTime;\n }\n\n get isPlaying(): boolean {\n return this.controller.playing;\n }\n\n on(event: string, handler: Function): void {\n this.eventBus.on(event as any, handler as any);\n }\n\n off(event: string, handler: Function): void {\n this.eventBus.off(event as any, handler as any);\n }\n}\n"],"names":[],"mappings":"AAYO,MAAM,kBAA2C;AAAA,EAC9C;AAAA,EACA;AAAA,EAER,YAAY,YAAgC,UAAqC;AAC/E,SAAK,aAAa;AAClB,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,OAAa;AACX,SAAK,WAAW,KAAA;AAAA,EAClB;AAAA,EAEA,KAAK,QAAsB;AACzB,SAAK,WAAW,KAAK,MAAM;AAAA,EAC7B;AAAA,EAEA,QAAQ,MAAoB;AAC1B,SAAK,WAAW,QAAQ,IAAI;AAAA,EAC9B;AAAA,EAEA,QAAc;AACZ,SAAK,WAAW,MAAA;AAAA,EAClB;AAAA,EAEA,SAAe;AACb,SAAK,WAAW,OAAA;AAAA,EAClB;AAAA,EAEA,OAAa;AACX,SAAK,WAAW,KAAA;AAAA,EAClB;AAAA,EAEA,UAAU,QAAsB;AAC9B,SAAK,WAAW,UAAU,MAAM;AAAA,EAClC;AAAA,EAEA,IAAI,gBAAwB;AAC1B,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA,EAEA,IAAI,YAAqB;AACvB,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA,EAEA,GAAG,OAAe,SAAyB;AACzC,SAAK,SAAS,GAAG,OAAc,OAAc;AAAA,EAC/C;AAAA,EAEA,IAAI,OAAe,SAAyB;AAC1C,SAAK,SAAS,IAAI,OAAc,OAAc;AAAA,EAChD;AACF;"}
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
function resolveDirtyRanges(patch, model) {
|
|
2
|
-
const affectedClips = /* @__PURE__ */ new Set();
|
|
3
|
-
const affectedTracks = /* @__PURE__ */ new Set();
|
|
4
|
-
const affectedResources = /* @__PURE__ */ new Set();
|
|
5
|
-
for (const op of patch.operations) {
|
|
6
|
-
collectAffectedElements(op, model, affectedClips, affectedTracks, affectedResources);
|
|
7
|
-
}
|
|
8
|
-
return calculateTimeRanges(affectedClips, affectedTracks, affectedResources, model);
|
|
9
|
-
}
|
|
10
|
-
function collectAffectedElements(op, model, affectedClips, affectedTracks, affectedResources) {
|
|
11
|
-
switch (op.type) {
|
|
12
|
-
case "addTrack":
|
|
13
|
-
case "removeTrack":
|
|
14
|
-
case "updateTrack": {
|
|
15
|
-
const trackOp = op;
|
|
16
|
-
if (trackOp.trackId) {
|
|
17
|
-
affectedTracks.add(trackOp.trackId);
|
|
18
|
-
const track = model.findTrack(trackOp.trackId);
|
|
19
|
-
if (track) {
|
|
20
|
-
track.clips.forEach((clip) => affectedClips.add(clip.id));
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
break;
|
|
24
|
-
}
|
|
25
|
-
case "addClip":
|
|
26
|
-
case "removeClip":
|
|
27
|
-
case "updateClip":
|
|
28
|
-
case "moveClip": {
|
|
29
|
-
const clipOp = op;
|
|
30
|
-
if (clipOp.clipId) {
|
|
31
|
-
affectedClips.add(clipOp.clipId);
|
|
32
|
-
}
|
|
33
|
-
if (clipOp.trackId) {
|
|
34
|
-
affectedTracks.add(clipOp.trackId);
|
|
35
|
-
}
|
|
36
|
-
if (clipOp.targetTrackId) {
|
|
37
|
-
affectedTracks.add(clipOp.targetTrackId);
|
|
38
|
-
}
|
|
39
|
-
if (clipOp.clip) {
|
|
40
|
-
collectOverlappingClips(
|
|
41
|
-
model,
|
|
42
|
-
clipOp.trackId,
|
|
43
|
-
clipOp.clip.startUs || 0,
|
|
44
|
-
(clipOp.clip.startUs || 0) + (clipOp.clip.durationUs || 0),
|
|
45
|
-
affectedClips
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
case "addResource":
|
|
51
|
-
case "updateResource":
|
|
52
|
-
case "removeResource": {
|
|
53
|
-
const resourceOp = op;
|
|
54
|
-
affectedResources.add(resourceOp.resourceId);
|
|
55
|
-
for (const track of model.tracks) {
|
|
56
|
-
for (const clip of track.clips) {
|
|
57
|
-
if (clip.resourceId === resourceOp.resourceId) {
|
|
58
|
-
affectedClips.add(clip.id);
|
|
59
|
-
affectedTracks.add(track.id);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
case "addAttachment":
|
|
66
|
-
case "updateAttachment":
|
|
67
|
-
case "removeAttachment": {
|
|
68
|
-
const attachmentOp = op;
|
|
69
|
-
if (attachmentOp.clipId) {
|
|
70
|
-
affectedClips.add(attachmentOp.clipId);
|
|
71
|
-
}
|
|
72
|
-
if (attachmentOp.trackId) {
|
|
73
|
-
affectedTracks.add(attachmentOp.trackId);
|
|
74
|
-
}
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
case "addTransition":
|
|
78
|
-
case "updateTransition":
|
|
79
|
-
case "removeTransition": {
|
|
80
|
-
const transitionOp = op;
|
|
81
|
-
if (transitionOp.clipId) {
|
|
82
|
-
affectedClips.add(transitionOp.clipId);
|
|
83
|
-
collectAdjacentClips(model, transitionOp.trackId, transitionOp.clipId, affectedClips);
|
|
84
|
-
}
|
|
85
|
-
if (transitionOp.trackId) {
|
|
86
|
-
affectedTracks.add(transitionOp.trackId);
|
|
87
|
-
}
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
case "addEffect":
|
|
91
|
-
case "updateEffect":
|
|
92
|
-
case "removeEffect": {
|
|
93
|
-
const effectOp = op;
|
|
94
|
-
if (effectOp.targetType === "track") {
|
|
95
|
-
affectedTracks.add(effectOp.targetId);
|
|
96
|
-
const track = model.findTrack(effectOp.targetId);
|
|
97
|
-
if (track) {
|
|
98
|
-
track.clips.forEach((clip) => affectedClips.add(clip.id));
|
|
99
|
-
}
|
|
100
|
-
} else if (effectOp.targetType === "clip") {
|
|
101
|
-
affectedClips.add(effectOp.targetId);
|
|
102
|
-
}
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
function collectOverlappingClips(model, trackId, startUs, endUs, affectedClips) {
|
|
108
|
-
const track = model.findTrack(trackId);
|
|
109
|
-
if (!track) return;
|
|
110
|
-
const overlapping = model.getActiveClips(startUs, endUs);
|
|
111
|
-
overlapping.forEach((clip) => affectedClips.add(clip.id));
|
|
112
|
-
}
|
|
113
|
-
function collectAdjacentClips(model, trackId, clipId, affectedClips) {
|
|
114
|
-
const track = model.findTrack(trackId);
|
|
115
|
-
if (!track) return;
|
|
116
|
-
const clipIndex = track.clips.findIndex((c) => c.id === clipId);
|
|
117
|
-
if (clipIndex === -1) return;
|
|
118
|
-
const prevClip = track.clips[clipIndex - 1];
|
|
119
|
-
if (clipIndex > 0 && prevClip) {
|
|
120
|
-
affectedClips.add(prevClip.id);
|
|
121
|
-
}
|
|
122
|
-
const nextClip = track.clips[clipIndex + 1];
|
|
123
|
-
if (clipIndex < track.clips.length - 1 && nextClip) {
|
|
124
|
-
affectedClips.add(nextClip.id);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
function calculateTimeRanges(affectedClips, affectedTracks, _affectedResources, model) {
|
|
128
|
-
const ranges = [];
|
|
129
|
-
const processedTracks = /* @__PURE__ */ new Set();
|
|
130
|
-
for (const clipId of affectedClips) {
|
|
131
|
-
const clip = model.findClip(clipId);
|
|
132
|
-
if (!clip) continue;
|
|
133
|
-
for (const track of model.tracks) {
|
|
134
|
-
if (track.clips.some((c) => c.id === clipId)) {
|
|
135
|
-
if (!processedTracks.has(track.id)) {
|
|
136
|
-
const range = calculateTrackDirtyRange(track.id, affectedClips, model);
|
|
137
|
-
if (range) {
|
|
138
|
-
ranges.push(range);
|
|
139
|
-
processedTracks.add(track.id);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
break;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
for (const trackId of affectedTracks) {
|
|
147
|
-
if (!processedTracks.has(trackId)) {
|
|
148
|
-
const track = model.findTrack(trackId);
|
|
149
|
-
if (track) {
|
|
150
|
-
ranges.push({
|
|
151
|
-
trackId,
|
|
152
|
-
startUs: 0,
|
|
153
|
-
endUs: model.getTrackDuration(trackId),
|
|
154
|
-
reason: "track_operation"
|
|
155
|
-
});
|
|
156
|
-
processedTracks.add(trackId);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return mergeRanges(ranges);
|
|
161
|
-
}
|
|
162
|
-
function calculateTrackDirtyRange(trackId, affectedClips, model) {
|
|
163
|
-
const track = model.findTrack(trackId);
|
|
164
|
-
if (!track) return null;
|
|
165
|
-
let minStartUs = Infinity;
|
|
166
|
-
let maxEndUs = 0;
|
|
167
|
-
let hasAffectedClips = false;
|
|
168
|
-
for (const clip of track.clips) {
|
|
169
|
-
if (affectedClips.has(clip.id)) {
|
|
170
|
-
hasAffectedClips = true;
|
|
171
|
-
minStartUs = Math.min(minStartUs, clip.startUs);
|
|
172
|
-
maxEndUs = Math.max(maxEndUs, clip.startUs + clip.durationUs);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if (!hasAffectedClips) return null;
|
|
176
|
-
return {
|
|
177
|
-
trackId,
|
|
178
|
-
startUs: minStartUs,
|
|
179
|
-
endUs: maxEndUs,
|
|
180
|
-
reason: "clips_affected"
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
function mergeRanges(ranges) {
|
|
184
|
-
if (ranges.length <= 1) return ranges;
|
|
185
|
-
const trackGroups = /* @__PURE__ */ new Map();
|
|
186
|
-
for (const range of ranges) {
|
|
187
|
-
const group = trackGroups.get(range.trackId) || [];
|
|
188
|
-
group.push(range);
|
|
189
|
-
trackGroups.set(range.trackId, group);
|
|
190
|
-
}
|
|
191
|
-
const merged = [];
|
|
192
|
-
for (const [trackId, trackRanges] of trackGroups) {
|
|
193
|
-
trackRanges.sort((a, b) => a.startUs - b.startUs);
|
|
194
|
-
let current = trackRanges[0];
|
|
195
|
-
for (let i = 1; i < trackRanges.length; i++) {
|
|
196
|
-
const next = trackRanges[i];
|
|
197
|
-
if (next && current && next.startUs <= current.endUs + 1e3) {
|
|
198
|
-
current = {
|
|
199
|
-
trackId,
|
|
200
|
-
startUs: current.startUs,
|
|
201
|
-
endUs: Math.max(current.endUs, next.endUs),
|
|
202
|
-
reason: `${current.reason},${next.reason}`
|
|
203
|
-
};
|
|
204
|
-
} else {
|
|
205
|
-
if (current) {
|
|
206
|
-
merged.push(current);
|
|
207
|
-
}
|
|
208
|
-
current = next;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (current) {
|
|
212
|
-
merged.push(current);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
return merged;
|
|
216
|
-
}
|
|
217
|
-
export {
|
|
218
|
-
resolveDirtyRanges
|
|
219
|
-
};
|
|
220
|
-
//# sourceMappingURL=dirty-range.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dirty-range.js","sources":["../../src/model/dirty-range.ts"],"sourcesContent":["import { CompositionModel } from './CompositionModel';\nimport { CompositionPatch, PatchOperation, DirtyRange, TimeUs } from './types';\n\nexport function resolveDirtyRanges(patch: CompositionPatch, model: CompositionModel): DirtyRange[] {\n const affectedClips = new Set<string>();\n const affectedTracks = new Set<string>();\n const affectedResources = new Set<string>();\n\n // Collect affected elements\n for (const op of patch.operations) {\n collectAffectedElements(op, model, affectedClips, affectedTracks, affectedResources);\n }\n\n // Calculate time ranges per track\n return calculateTimeRanges(affectedClips, affectedTracks, affectedResources, model);\n}\n\nfunction collectAffectedElements(\n op: PatchOperation,\n model: CompositionModel,\n affectedClips: Set<string>,\n affectedTracks: Set<string>,\n affectedResources: Set<string>\n): void {\n switch (op.type) {\n // Track-level operations affect all clips in the track\n case 'addTrack':\n case 'removeTrack':\n case 'updateTrack': {\n const trackOp = op as any;\n if (trackOp.trackId) {\n affectedTracks.add(trackOp.trackId);\n // Collect all clips in this track\n const track = model.findTrack(trackOp.trackId);\n if (track) {\n track.clips.forEach((clip) => affectedClips.add(clip.id));\n }\n }\n break;\n }\n\n // Clip-level operations\n case 'addClip':\n case 'removeClip':\n case 'updateClip':\n case 'moveClip': {\n const clipOp = op as any;\n if (clipOp.clipId) {\n affectedClips.add(clipOp.clipId);\n }\n if (clipOp.trackId) {\n affectedTracks.add(clipOp.trackId);\n }\n if (clipOp.targetTrackId) {\n affectedTracks.add(clipOp.targetTrackId);\n }\n // Check for overlapping clips\n if (clipOp.clip) {\n collectOverlappingClips(\n model,\n clipOp.trackId,\n clipOp.clip.startUs || 0,\n (clipOp.clip.startUs || 0) + (clipOp.clip.durationUs || 0),\n affectedClips\n );\n }\n break;\n }\n\n // Resource changes affect all clips using that resource\n case 'addResource':\n case 'updateResource':\n case 'removeResource': {\n const resourceOp = op as any;\n affectedResources.add(resourceOp.resourceId);\n // Find all clips using this resource\n for (const track of model.tracks) {\n for (const clip of track.clips) {\n if (clip.resourceId === resourceOp.resourceId) {\n affectedClips.add(clip.id);\n affectedTracks.add(track.id);\n }\n }\n }\n break;\n }\n\n // Attachment operations\n case 'addAttachment':\n case 'updateAttachment':\n case 'removeAttachment': {\n const attachmentOp = op as any;\n if (attachmentOp.clipId) {\n affectedClips.add(attachmentOp.clipId);\n }\n if (attachmentOp.trackId) {\n affectedTracks.add(attachmentOp.trackId);\n }\n break;\n }\n\n // Transition operations\n case 'addTransition':\n case 'updateTransition':\n case 'removeTransition': {\n const transitionOp = op as any;\n if (transitionOp.clipId) {\n affectedClips.add(transitionOp.clipId);\n // Transitions might affect adjacent clips\n collectAdjacentClips(model, transitionOp.trackId, transitionOp.clipId, affectedClips);\n }\n if (transitionOp.trackId) {\n affectedTracks.add(transitionOp.trackId);\n }\n break;\n }\n\n // Effect operations\n case 'addEffect':\n case 'updateEffect':\n case 'removeEffect': {\n const effectOp = op as any;\n if (effectOp.targetType === 'track') {\n affectedTracks.add(effectOp.targetId);\n // Track effects affect all clips in the track\n const track = model.findTrack(effectOp.targetId);\n if (track) {\n track.clips.forEach((clip) => affectedClips.add(clip.id));\n }\n } else if (effectOp.targetType === 'clip') {\n affectedClips.add(effectOp.targetId);\n }\n break;\n }\n }\n}\n\nfunction collectOverlappingClips(\n model: CompositionModel,\n trackId: string,\n startUs: TimeUs,\n endUs: TimeUs,\n affectedClips: Set<string>\n): void {\n const track = model.findTrack(trackId);\n if (!track) return;\n\n // Use binary search to find overlapping clips efficiently\n const overlapping = model.getActiveClips(startUs, endUs);\n overlapping.forEach((clip) => affectedClips.add(clip.id));\n}\n\nfunction collectAdjacentClips(\n model: CompositionModel,\n trackId: string,\n clipId: string,\n affectedClips: Set<string>\n): void {\n const track = model.findTrack(trackId);\n if (!track) return;\n\n const clipIndex = track.clips.findIndex((c) => c.id === clipId);\n if (clipIndex === -1) return;\n\n // Add previous clip if exists\n const prevClip = track.clips[clipIndex - 1];\n if (clipIndex > 0 && prevClip) {\n affectedClips.add(prevClip.id);\n }\n\n // Add next clip if exists\n const nextClip = track.clips[clipIndex + 1];\n if (clipIndex < track.clips.length - 1 && nextClip) {\n affectedClips.add(nextClip.id);\n }\n}\n\nfunction calculateTimeRanges(\n affectedClips: Set<string>,\n affectedTracks: Set<string>,\n _affectedResources: Set<string>,\n model: CompositionModel\n): DirtyRange[] {\n const ranges: DirtyRange[] = [];\n const processedTracks = new Set<string>();\n\n // Process affected clips\n for (const clipId of affectedClips) {\n const clip = model.findClip(clipId);\n if (!clip) continue;\n\n // Find the track containing this clip\n for (const track of model.tracks) {\n if (track.clips.some((c) => c.id === clipId)) {\n if (!processedTracks.has(track.id)) {\n const range = calculateTrackDirtyRange(track.id, affectedClips, model);\n if (range) {\n ranges.push(range);\n processedTracks.add(track.id);\n }\n }\n break;\n }\n }\n }\n\n // Process tracks that were directly affected but haven't been processed yet\n for (const trackId of affectedTracks) {\n if (!processedTracks.has(trackId)) {\n const track = model.findTrack(trackId);\n if (track) {\n ranges.push({\n trackId,\n startUs: 0,\n endUs: model.getTrackDuration(trackId),\n reason: 'track_operation',\n });\n processedTracks.add(trackId);\n }\n }\n }\n\n return mergeRanges(ranges);\n}\n\nfunction calculateTrackDirtyRange(\n trackId: string,\n affectedClips: Set<string>,\n model: CompositionModel\n): DirtyRange | null {\n const track = model.findTrack(trackId);\n if (!track) return null;\n\n let minStartUs = Infinity;\n let maxEndUs = 0;\n let hasAffectedClips = false;\n\n for (const clip of track.clips) {\n if (affectedClips.has(clip.id)) {\n hasAffectedClips = true;\n minStartUs = Math.min(minStartUs, clip.startUs);\n maxEndUs = Math.max(maxEndUs, clip.startUs + clip.durationUs);\n }\n }\n\n if (!hasAffectedClips) return null;\n\n return {\n trackId,\n startUs: minStartUs,\n endUs: maxEndUs,\n reason: 'clips_affected',\n };\n}\n\nfunction mergeRanges(ranges: DirtyRange[]): DirtyRange[] {\n if (ranges.length <= 1) return ranges;\n\n // Group by trackId\n const trackGroups = new Map<string, DirtyRange[]>();\n for (const range of ranges) {\n const group = trackGroups.get(range.trackId) || [];\n group.push(range);\n trackGroups.set(range.trackId, group);\n }\n\n // Merge overlapping ranges within each track\n const merged: DirtyRange[] = [];\n for (const [trackId, trackRanges] of trackGroups) {\n trackRanges.sort((a, b) => a.startUs - b.startUs);\n\n let current = trackRanges[0];\n for (let i = 1; i < trackRanges.length; i++) {\n const next = trackRanges[i];\n // Merge if overlapping or adjacent (within 1ms)\n if (next && current && next.startUs <= current.endUs + 1000) {\n current = {\n trackId,\n startUs: current.startUs,\n endUs: Math.max(current.endUs, next.endUs),\n reason: `${current.reason},${next.reason}`,\n };\n } else {\n if (current) {\n merged.push(current);\n }\n current = next;\n }\n }\n if (current) {\n merged.push(current);\n }\n }\n\n return merged;\n}\n"],"names":[],"mappings":"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"}
|
package/dist/model/types.js
DELETED
package/dist/model/types.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sources":["../../src/model/types.ts"],"sourcesContent":["// All time values in microseconds (µs)\nexport type TimeUs = number; // 1 second = 1_000_000 µs\n\n// Helper constants\nexport const MICROSECONDS_PER_SECOND = 1_000_000;\nexport const MICROSECONDS_PER_MILLISECOND = 1_000;\n\n// ────── Root Object ──────\nexport interface CompositionModelData {\n version: '1.0';\n fps: 24 | 25 | 30 | 60;\n durationUs: TimeUs;\n\n tracks: Track[];\n resources: Record<string, Resource>;\n\n renderConfig?: RenderConfig;\n\n ext?: Record<string, unknown>;\n}\n\nexport interface RenderConfig {\n width: number;\n height: number;\n backgroundColor?: string;\n}\n\n// ────── Track ──────\nexport interface Track {\n id: string;\n kind: 'video' | 'audio' | 'caption' | 'fx';\n clips: Clip[];\n\n effects?: Effect[];\n duckingRules?: DuckingRule[];\n}\n\n// ────── Clip ──────\nexport interface Clip {\n id: string;\n resourceId: string;\n startUs: TimeUs;\n durationUs: TimeUs;\n trackId?: string;\n trackKind?: 'video' | 'audio' | 'caption' | 'fx';\n\n trimStartUs?: TimeUs;\n trimEndUs?: TimeUs;\n\n effects?: Effect[];\n attachments?: Attachment[];\n\n transitionIn?: Transition;\n transitionOut?: Transition;\n}\n\n// ────── Resource ──────\nexport interface Resource {\n id: string;\n type: 'video' | 'image' | 'audio' | 'json' | string;\n uri: string;\n metadata?: Record<string, unknown>;\n clipIds?: string[];\n // Runtime state maintained by engine\n state?: 'pending' | 'loading' | 'ready' | 'error';\n}\n\n// ────── Common Structures ──────\nexport interface Effect {\n id: string;\n effectType: 'filter' | 'lut' | 'animation' | string;\n params?: Record<string, unknown>;\n}\n\nexport interface Transition {\n id: string;\n transitionType: 'fade' | 'wipe' | 'slide' | string;\n durationUs: TimeUs;\n curve?: 'linear' | 'ease-in' | 'ease-out' | string;\n params?: Record<string, unknown>;\n}\n\nexport interface Attachment {\n id: string;\n kind: 'caption' | 'sticker' | 'mask' | string;\n startUs: TimeUs;\n durationUs: TimeUs;\n data: Record<string, unknown>;\n}\n\nexport interface DuckingRule {\n targetTrackKind: 'voice' | 'audio' | string;\n ratio: number;\n attackMs: number;\n releaseMs: number;\n}\n\n// ────── Patch System ──────\nexport interface CompositionPatch {\n operations: PatchOperation[];\n metadata?: {\n timestamp: number;\n source?: string;\n version?: string;\n };\n}\n\nexport type PatchOperation =\n | TrackOperation\n | ClipOperation\n | ResourceOperation\n | AttachmentOperation\n | TransitionOperation\n | EffectOperation\n | RenderConfigOperation;\n\n// Track operations\nexport interface TrackOperation {\n type: 'addTrack' | 'updateTrack' | 'removeTrack';\n trackId?: string;\n track?: Partial<Track>;\n}\n\n// Clip operations\nexport interface ClipOperation {\n type: 'addClip' | 'updateClip' | 'removeClip' | 'moveClip';\n trackId: string;\n clipId?: string;\n clip?: Partial<Clip>;\n targetTrackId?: string;\n targetStartUs?: TimeUs;\n}\n\n// Resource operations\nexport interface ResourceOperation {\n type: 'addResource' | 'updateResource' | 'removeResource';\n resourceId: string;\n resource?: Partial<Resource>;\n}\n\n// Attachment operations\nexport interface AttachmentOperation {\n type: 'addAttachment' | 'updateAttachment' | 'removeAttachment';\n trackId: string;\n clipId: string;\n attachmentId?: string;\n attachment?: Partial<Attachment>;\n}\n\n// Transition operations\nexport interface TransitionOperation {\n type: 'addTransition' | 'updateTransition' | 'removeTransition';\n trackId: string;\n clipId: string;\n position: 'in' | 'out';\n transition?: Partial<Transition>;\n}\n\n// Render config operations\nexport interface RenderConfigOperation {\n type: 'updateRenderConfig';\n renderConfig?: Partial<RenderConfig>;\n}\n\n// Effect operations\nexport interface EffectOperation {\n type: 'addEffect' | 'updateEffect' | 'removeEffect';\n targetType: 'track' | 'clip';\n targetId: string;\n effectId?: string;\n effect?: Partial<Effect>;\n}\n\n// ────── Dirty Range ──────\nexport interface DirtyRange {\n trackId: string;\n startUs: TimeUs;\n endUs: TimeUs;\n reason: string;\n}\n\n// ────── Validation ──────\nexport interface ValidationError {\n path: string;\n message: string;\n value: any;\n}\n"],"names":[],"mappings":"AAIO,MAAM,0BAA0B;"}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
class BackpressureMonitor {
|
|
2
|
-
metrics = /* @__PURE__ */ new Map();
|
|
3
|
-
/**
|
|
4
|
-
* Update metrics for a stage
|
|
5
|
-
*/
|
|
6
|
-
updateMetrics(stage, desiredSize, queueSize = 0) {
|
|
7
|
-
const isPaused = desiredSize <= 0;
|
|
8
|
-
this.metrics.set(stage, {
|
|
9
|
-
desiredSize,
|
|
10
|
-
queueSize,
|
|
11
|
-
isPaused,
|
|
12
|
-
lastUpdate: Date.now()
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Get current metrics snapshot
|
|
17
|
-
*/
|
|
18
|
-
getSnapshot() {
|
|
19
|
-
const now = Date.now();
|
|
20
|
-
const snapshot = {};
|
|
21
|
-
for (const [stage, metrics] of this.metrics) {
|
|
22
|
-
snapshot[stage] = {
|
|
23
|
-
...metrics,
|
|
24
|
-
age: now - metrics.lastUpdate
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
return snapshot;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Check if any stage is experiencing backpressure
|
|
31
|
-
*/
|
|
32
|
-
hasBackpressure() {
|
|
33
|
-
for (const metrics of this.metrics.values()) {
|
|
34
|
-
if (metrics.isPaused) {
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Get stages currently experiencing backpressure
|
|
42
|
-
*/
|
|
43
|
-
getBottlenecks() {
|
|
44
|
-
const bottlenecks = [];
|
|
45
|
-
for (const [stage, metrics] of this.metrics) {
|
|
46
|
-
if (metrics.isPaused) {
|
|
47
|
-
bottlenecks.push(stage);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return bottlenecks;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Clear all metrics
|
|
54
|
-
*/
|
|
55
|
-
clear() {
|
|
56
|
-
this.metrics.clear();
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
export {
|
|
60
|
-
BackpressureMonitor
|
|
61
|
-
};
|
|
62
|
-
//# sourceMappingURL=BackpressureMonitor.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BackpressureMonitor.js","sources":["../../src/plugins/BackpressureMonitor.ts"],"sourcesContent":["/**\n * Monitor and report backpressure status across pipeline stages\n * This is a runtime monitoring tool used by plugins\n */\nexport class BackpressureMonitor {\n private metrics = new Map<\n string,\n {\n desiredSize: number;\n queueSize: number;\n isPaused: boolean;\n lastUpdate: number;\n }\n >();\n\n /**\n * Update metrics for a stage\n */\n updateMetrics(stage: string, desiredSize: number, queueSize: number = 0): void {\n const isPaused = desiredSize <= 0;\n this.metrics.set(stage, {\n desiredSize,\n queueSize,\n isPaused,\n lastUpdate: Date.now(),\n });\n }\n\n /**\n * Get current metrics snapshot\n */\n getSnapshot(): Record<\n string,\n {\n desiredSize: number;\n queueSize: number;\n isPaused: boolean;\n age: number;\n }\n > {\n const now = Date.now();\n const snapshot: Record<string, any> = {};\n\n for (const [stage, metrics] of this.metrics) {\n snapshot[stage] = {\n ...metrics,\n age: now - metrics.lastUpdate,\n };\n }\n\n return snapshot;\n }\n\n /**\n * Check if any stage is experiencing backpressure\n */\n hasBackpressure(): boolean {\n for (const metrics of this.metrics.values()) {\n if (metrics.isPaused) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Get stages currently experiencing backpressure\n */\n getBottlenecks(): string[] {\n const bottlenecks: string[] = [];\n for (const [stage, metrics] of this.metrics) {\n if (metrics.isPaused) {\n bottlenecks.push(stage);\n }\n }\n return bottlenecks;\n }\n\n /**\n * Clear all metrics\n */\n clear(): void {\n this.metrics.clear();\n }\n}\n"],"names":[],"mappings":"AAIO,MAAM,oBAAoB;AAAA,EACvB,8BAAc,IAAA;AAAA;AAAA;AAAA;AAAA,EAatB,cAAc,OAAe,aAAqB,YAAoB,GAAS;AAC7E,UAAM,WAAW,eAAe;AAChC,SAAK,QAAQ,IAAI,OAAO;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY,KAAK,IAAA;AAAA,IAAI,CACtB;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,cAQE;AACA,UAAM,MAAM,KAAK,IAAA;AACjB,UAAM,WAAgC,CAAA;AAEtC,eAAW,CAAC,OAAO,OAAO,KAAK,KAAK,SAAS;AAC3C,eAAS,KAAK,IAAI;AAAA,QAChB,GAAG;AAAA,QACH,KAAK,MAAM,QAAQ;AAAA,MAAA;AAAA,IAEvB;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,kBAA2B;AACzB,eAAW,WAAW,KAAK,QAAQ,OAAA,GAAU;AAC3C,UAAI,QAAQ,UAAU;AACpB,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,iBAA2B;AACzB,UAAM,cAAwB,CAAA;AAC9B,eAAW,CAAC,OAAO,OAAO,KAAK,KAAK,SAAS;AAC3C,UAAI,QAAQ,UAAU;AACpB,oBAAY,KAAK,KAAK;AAAA,MACxB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,QAAQ,MAAA;AAAA,EACf;AACF;"}
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
class AudioDucker {
|
|
2
|
-
config = null;
|
|
3
|
-
sampleRate;
|
|
4
|
-
constructor(sampleRate) {
|
|
5
|
-
this.sampleRate = sampleRate;
|
|
6
|
-
}
|
|
7
|
-
configure(config) {
|
|
8
|
-
this.config = config;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Analyze trigger tracks (voice) and generate ducking envelope
|
|
12
|
-
* Returns gain values (0-1) to apply to target tracks (BGM)
|
|
13
|
-
*/
|
|
14
|
-
async generateDuckingEnvelope(tracks, frameCount) {
|
|
15
|
-
if (!this.config?.enabled) {
|
|
16
|
-
return new Float32Array(frameCount).fill(1);
|
|
17
|
-
}
|
|
18
|
-
const envelope = new Float32Array(frameCount);
|
|
19
|
-
envelope.fill(1);
|
|
20
|
-
const triggerTracks = tracks.filter((t) => this.config.triggerTracks.includes(t.trackId));
|
|
21
|
-
if (triggerTracks.length === 0) {
|
|
22
|
-
return envelope;
|
|
23
|
-
}
|
|
24
|
-
for (const track of triggerTracks) {
|
|
25
|
-
const voiceActivity = await this.detectVoiceActivity(track.audioData);
|
|
26
|
-
this.applyDuckingToEnvelope(envelope, voiceActivity);
|
|
27
|
-
}
|
|
28
|
-
return envelope;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Voice Activity Detection (VAD)
|
|
32
|
-
* Simple energy-based detection with smoothing
|
|
33
|
-
* More sophisticated implementations could use:
|
|
34
|
-
* - Zero-crossing rate (ZCR) for speech/music discrimination
|
|
35
|
-
* - Spectral centroid for voice frequency detection
|
|
36
|
-
* - Machine learning models for robust VAD
|
|
37
|
-
*/
|
|
38
|
-
async detectVoiceActivity(audioData) {
|
|
39
|
-
const frameCount = audioData.numberOfFrames;
|
|
40
|
-
const activity = new Float32Array(frameCount);
|
|
41
|
-
const monoData = new Float32Array(frameCount);
|
|
42
|
-
const channelData = new Float32Array(frameCount);
|
|
43
|
-
for (let ch = 0; ch < audioData.numberOfChannels; ch++) {
|
|
44
|
-
audioData.copyTo(channelData, {
|
|
45
|
-
planeIndex: ch,
|
|
46
|
-
format: "f32-planar"
|
|
47
|
-
});
|
|
48
|
-
for (let i = 0; i < frameCount; i++) {
|
|
49
|
-
if (monoData && channelData) {
|
|
50
|
-
monoData[i] = (monoData[i] || 0) + (channelData[i] || 0) / audioData.numberOfChannels;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
const windowSize = Math.floor(this.sampleRate * 0.02);
|
|
55
|
-
const hopSize = Math.floor(windowSize / 2);
|
|
56
|
-
for (let i = 0; i < frameCount; i += hopSize) {
|
|
57
|
-
const end = Math.min(i + windowSize, frameCount);
|
|
58
|
-
let energy = 0;
|
|
59
|
-
for (let j = i; j < end; j++) {
|
|
60
|
-
if (monoData && monoData[j] !== void 0) {
|
|
61
|
-
const sample = monoData[j];
|
|
62
|
-
if (sample !== void 0) {
|
|
63
|
-
energy += sample * sample;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
energy = Math.sqrt(energy / (end - i));
|
|
68
|
-
const threshold = 0.01;
|
|
69
|
-
const isVoice = energy > threshold;
|
|
70
|
-
for (let j = i; j < end; j++) {
|
|
71
|
-
activity[j] = isVoice ? 1 : 0;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return this.smoothActivityDetection(activity);
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Smooth voice activity detection to avoid choppy ducking
|
|
78
|
-
* Uses a simple moving average filter
|
|
79
|
-
*/
|
|
80
|
-
smoothActivityDetection(activity) {
|
|
81
|
-
const smoothed = new Float32Array(activity.length);
|
|
82
|
-
const smoothWindow = Math.floor(this.sampleRate * 0.05);
|
|
83
|
-
for (let i = 0; i < activity.length; i++) {
|
|
84
|
-
let sum = 0;
|
|
85
|
-
let count = 0;
|
|
86
|
-
for (let j = Math.max(0, i - smoothWindow); j <= Math.min(activity.length - 1, i + smoothWindow); j++) {
|
|
87
|
-
if (activity && activity[j] !== void 0) {
|
|
88
|
-
const val = activity[j];
|
|
89
|
-
if (val !== void 0) {
|
|
90
|
-
sum += val;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
count++;
|
|
94
|
-
}
|
|
95
|
-
smoothed[i] = sum / count;
|
|
96
|
-
}
|
|
97
|
-
return smoothed;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Apply ducking based on voice activity
|
|
101
|
-
* Implements attack/release envelope shaping
|
|
102
|
-
*/
|
|
103
|
-
applyDuckingToEnvelope(envelope, voiceActivity) {
|
|
104
|
-
if (!this.config) return;
|
|
105
|
-
const duckingLevel = 1 - this.config.duckingLevel;
|
|
106
|
-
const attackSamples = Math.floor(this.config.attackTimeMs / 1e3 * this.sampleRate);
|
|
107
|
-
const releaseSamples = Math.floor(this.config.releaseTimeMs / 1e3 * this.sampleRate);
|
|
108
|
-
const lookAheadSamples = this.config.lookAheadMs ? Math.floor(this.config.lookAheadMs / 1e3 * this.sampleRate) : 0;
|
|
109
|
-
let currentGain = 1;
|
|
110
|
-
let releaseCounter = 0;
|
|
111
|
-
for (let i = 0; i < envelope.length; i++) {
|
|
112
|
-
const lookAheadIndex = Math.min(i + lookAheadSamples, voiceActivity.length - 1);
|
|
113
|
-
const activity = voiceActivity[lookAheadIndex];
|
|
114
|
-
if (activity !== void 0 && activity > 0.5) {
|
|
115
|
-
if (currentGain > duckingLevel) {
|
|
116
|
-
currentGain = Math.max(duckingLevel, currentGain - (1 - duckingLevel) / attackSamples);
|
|
117
|
-
} else {
|
|
118
|
-
currentGain = duckingLevel;
|
|
119
|
-
}
|
|
120
|
-
releaseCounter = 0;
|
|
121
|
-
} else if (currentGain < 1) {
|
|
122
|
-
releaseCounter++;
|
|
123
|
-
if (releaseCounter > releaseSamples * 0.1) {
|
|
124
|
-
currentGain = Math.min(1, currentGain + (1 - duckingLevel) / releaseSamples);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
envelope[i] = Math.min(envelope[i] || 1, currentGain);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Apply ducking envelope to audio buffer
|
|
132
|
-
* This modulates the volume over time according to the envelope
|
|
133
|
-
*/
|
|
134
|
-
applyEnvelopeToVolume(baseVolume, envelope) {
|
|
135
|
-
const result = new Float32Array(envelope.length);
|
|
136
|
-
for (let i = 0; i < envelope.length; i++) {
|
|
137
|
-
result[i] = baseVolume * (envelope[i] || 1);
|
|
138
|
-
}
|
|
139
|
-
return result;
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Calculate dynamic range to avoid over-compression
|
|
143
|
-
* Returns the difference between peak and RMS levels in dB
|
|
144
|
-
*/
|
|
145
|
-
calculateDynamicRange(envelope) {
|
|
146
|
-
let peak = 0;
|
|
147
|
-
let sumSquares = 0;
|
|
148
|
-
for (const value of envelope) {
|
|
149
|
-
peak = Math.max(peak, value);
|
|
150
|
-
sumSquares += value * value;
|
|
151
|
-
}
|
|
152
|
-
const rms = Math.sqrt(sumSquares / envelope.length);
|
|
153
|
-
const peakDb = 20 * Math.log10(peak);
|
|
154
|
-
const rmsDb = 20 * Math.log10(rms);
|
|
155
|
-
return peakDb - rmsDb;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
export {
|
|
159
|
-
AudioDucker
|
|
160
|
-
};
|
|
161
|
-
//# sourceMappingURL=AudioDucker.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AudioDucker.js","sources":["../../../src/stages/compose/AudioDucker.ts"],"sourcesContent":["import type { DuckingConfig, MixRequest } from './types';\n\n/**\n * AudioDucker - Automatic volume ducking for background music\n *\n * Ducking: Audio engineering technique where the volume of one audio source\n * is automatically reduced when another audio source is present.\n *\n * Common use case: Reduce background music volume when voice/narration plays\n * to improve speech intelligibility without completely muting the music.\n *\n * Key parameters:\n * - Threshold: Level at which ducking triggers\n * - Ratio: How much to reduce volume (e.g., 3:1 means reduce by 1/3)\n * - Attack: How quickly volume reduces (typically 10-50ms)\n * - Release: How quickly volume returns to normal (typically 100-500ms)\n * - Hold: Time to maintain ducking after trigger ends\n */\nexport class AudioDucker {\n private config: DuckingConfig | null = null;\n private sampleRate: number;\n\n constructor(sampleRate: number) {\n this.sampleRate = sampleRate;\n }\n\n configure(config: DuckingConfig): void {\n this.config = config;\n }\n\n /**\n * Analyze trigger tracks (voice) and generate ducking envelope\n * Returns gain values (0-1) to apply to target tracks (BGM)\n */\n async generateDuckingEnvelope(\n tracks: MixRequest['tracks'],\n frameCount: number\n ): Promise<Float32Array> {\n if (!this.config?.enabled) {\n return new Float32Array(frameCount).fill(1.0);\n }\n\n // Initialize envelope with no ducking (gain = 1.0)\n const envelope = new Float32Array(frameCount);\n envelope.fill(1.0);\n\n // Find trigger tracks (typically voice/narration)\n const triggerTracks = tracks.filter((t) => this.config!.triggerTracks.includes(t.trackId));\n\n if (triggerTracks.length === 0) {\n return envelope;\n }\n\n // Analyze each trigger track for voice activity\n for (const track of triggerTracks) {\n const voiceActivity = await this.detectVoiceActivity(track.audioData);\n this.applyDuckingToEnvelope(envelope, voiceActivity);\n }\n\n return envelope;\n }\n\n /**\n * Voice Activity Detection (VAD)\n * Simple energy-based detection with smoothing\n * More sophisticated implementations could use:\n * - Zero-crossing rate (ZCR) for speech/music discrimination\n * - Spectral centroid for voice frequency detection\n * - Machine learning models for robust VAD\n */\n private async detectVoiceActivity(audioData: AudioData): Promise<Float32Array> {\n const frameCount = audioData.numberOfFrames;\n const activity = new Float32Array(frameCount);\n\n // Convert to mono for analysis\n const monoData = new Float32Array(frameCount);\n const channelData = new Float32Array(frameCount);\n\n for (let ch = 0; ch < audioData.numberOfChannels; ch++) {\n audioData.copyTo(channelData, {\n planeIndex: ch,\n format: 'f32-planar' as const,\n });\n\n for (let i = 0; i < frameCount; i++) {\n if (monoData && channelData) {\n monoData[i] = (monoData[i] || 0) + (channelData[i] || 0) / audioData.numberOfChannels;\n }\n }\n }\n\n // Energy calculation with windowing\n // Window size: 20ms is typical for speech analysis\n const windowSize = Math.floor(this.sampleRate * 0.02);\n const hopSize = Math.floor(windowSize / 2); // 50% overlap\n\n for (let i = 0; i < frameCount; i += hopSize) {\n const end = Math.min(i + windowSize, frameCount);\n\n // Calculate RMS energy in window\n let energy = 0;\n for (let j = i; j < end; j++) {\n if (monoData && monoData[j] !== undefined) {\n const sample = monoData[j];\n if (sample !== undefined) {\n energy += sample * sample;\n }\n }\n }\n energy = Math.sqrt(energy / (end - i));\n\n // Simple threshold-based VAD\n // Typical speech energy threshold: -40dB to -30dB\n const threshold = 0.01; // Approximately -40dB\n const isVoice = energy > threshold;\n\n // Fill activity array for this window\n for (let j = i; j < end; j++) {\n activity[j] = isVoice ? 1.0 : 0.0;\n }\n }\n\n // Smooth activity detection to avoid rapid changes\n return this.smoothActivityDetection(activity);\n }\n\n /**\n * Smooth voice activity detection to avoid choppy ducking\n * Uses a simple moving average filter\n */\n private smoothActivityDetection(activity: Float32Array): Float32Array {\n const smoothed = new Float32Array(activity.length);\n const smoothWindow = Math.floor(this.sampleRate * 0.05); // 50ms smoothing\n\n for (let i = 0; i < activity.length; i++) {\n let sum = 0;\n let count = 0;\n\n for (\n let j = Math.max(0, i - smoothWindow);\n j <= Math.min(activity.length - 1, i + smoothWindow);\n j++\n ) {\n if (activity && activity[j] !== undefined) {\n const val = activity[j];\n if (val !== undefined) {\n sum += val;\n }\n }\n count++;\n }\n\n smoothed[i] = sum / count;\n }\n\n return smoothed;\n }\n\n /**\n * Apply ducking based on voice activity\n * Implements attack/release envelope shaping\n */\n private applyDuckingToEnvelope(envelope: Float32Array, voiceActivity: Float32Array): void {\n if (!this.config) return;\n\n const duckingLevel = 1.0 - this.config.duckingLevel;\n const attackSamples = Math.floor((this.config.attackTimeMs / 1000) * this.sampleRate);\n const releaseSamples = Math.floor((this.config.releaseTimeMs / 1000) * this.sampleRate);\n const lookAheadSamples = this.config.lookAheadMs\n ? Math.floor((this.config.lookAheadMs / 1000) * this.sampleRate)\n : 0;\n\n let currentGain = 1.0;\n let releaseCounter = 0;\n\n for (let i = 0; i < envelope.length; i++) {\n // Look ahead for upcoming voice activity\n const lookAheadIndex = Math.min(i + lookAheadSamples, voiceActivity.length - 1);\n const activity = voiceActivity[lookAheadIndex];\n\n if (activity !== undefined && activity > 0.5) {\n // Voice detected - apply ducking with attack curve\n if (currentGain > duckingLevel) {\n // Attack phase - reduce gain\n currentGain = Math.max(duckingLevel, currentGain - (1.0 - duckingLevel) / attackSamples);\n } else {\n currentGain = duckingLevel;\n }\n releaseCounter = 0;\n } else if (currentGain < 1.0) {\n // No voice - apply release curve\n releaseCounter++;\n if (releaseCounter > releaseSamples * 0.1) {\n // Small hold time\n currentGain = Math.min(1.0, currentGain + (1.0 - duckingLevel) / releaseSamples);\n }\n }\n\n // Apply the calculated gain\n envelope[i] = Math.min(envelope[i] || 1, currentGain);\n }\n }\n\n /**\n * Apply ducking envelope to audio buffer\n * This modulates the volume over time according to the envelope\n */\n applyEnvelopeToVolume(baseVolume: number, envelope: Float32Array): Float32Array {\n const result = new Float32Array(envelope.length);\n for (let i = 0; i < envelope.length; i++) {\n result[i] = baseVolume * (envelope[i] || 1);\n }\n return result;\n }\n\n /**\n * Calculate dynamic range to avoid over-compression\n * Returns the difference between peak and RMS levels in dB\n */\n calculateDynamicRange(envelope: Float32Array): number {\n let peak = 0;\n let sumSquares = 0;\n\n for (const value of envelope) {\n peak = Math.max(peak, value);\n sumSquares += value * value;\n }\n\n const rms = Math.sqrt(sumSquares / envelope.length);\n\n // Convert to dB (20 * log10(ratio))\n const peakDb = 20 * Math.log10(peak);\n const rmsDb = 20 * Math.log10(rms);\n\n return peakDb - rmsDb;\n }\n}\n"],"names":[],"mappings":"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"}
|