@meframe/core 0.2.5 → 0.2.6
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/medeo-fe/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js.map +1 -0
- package/dist/{node_modules → medeo-fe/node_modules}/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js +2 -2
- package/dist/medeo-fe/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js.map +1 -0
- package/dist/model/types.d.ts +7 -0
- package/dist/model/types.d.ts.map +1 -1
- package/dist/model/types.js.map +1 -1
- package/dist/orchestrator/AudioWindowPreparer.d.ts.map +1 -1
- package/dist/orchestrator/AudioWindowPreparer.js +23 -3
- package/dist/orchestrator/AudioWindowPreparer.js.map +1 -1
- package/dist/stages/compose/OfflineAudioMixer.d.ts.map +1 -1
- package/dist/stages/compose/OfflineAudioMixer.js +47 -28
- package/dist/stages/compose/OfflineAudioMixer.js.map +1 -1
- package/dist/stages/mux/MP4Muxer.js +1 -1
- package/dist/utils/loop-utils.d.ts +16 -0
- package/dist/utils/loop-utils.d.ts.map +1 -0
- package/dist/utils/loop-utils.js +44 -0
- package/dist/utils/loop-utils.js.map +1 -0
- package/dist/utils/mp4box.js +1 -1
- package/dist/workers/MP4Demuxer.DfWiwyjB.js.map +1 -1
- package/package.json +1 -1
- package/dist/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js.map +0 -1
- package/dist/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js.map +0 -1
- /package/dist/{node_modules → medeo-fe/node_modules}/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js +0 -0
package/dist/model/types.d.ts
CHANGED
|
@@ -33,6 +33,13 @@ interface BaseClip {
|
|
|
33
33
|
durationUs: TimeUs;
|
|
34
34
|
trackId?: string;
|
|
35
35
|
trimStartUs?: TimeUs;
|
|
36
|
+
/**
|
|
37
|
+
* Loop the underlying media content to fill this clip's duration.
|
|
38
|
+
*
|
|
39
|
+
* - AudioClip: loop audio samples
|
|
40
|
+
* - VideoClip: (future) loop video frames (and embedded audio) together
|
|
41
|
+
*/
|
|
42
|
+
loop?: boolean;
|
|
36
43
|
effects?: Effect[];
|
|
37
44
|
attachments?: Attachment[];
|
|
38
45
|
transitionIn?: Transition;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/model/types.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAG5B,eAAO,MAAM,uBAAuB,UAAY,CAAC;AACjD,eAAO,MAAM,4BAA4B,OAAQ,CAAC;AAGlD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,KAAK,CAAC;IACf,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,YAAY,CAAC;IAE5B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAGD,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,IAAI,CAAC;IACvD,KAAK,EAAE,IAAI,EAAE,CAAC;IAEd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;CAC9B;AAKD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,WAAW,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/model/types.ts"],"names":[],"mappings":"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"}
|
package/dist/model/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 tracks: Track[];\n resources: Record<string, Resource>;\n\n mainTrackId?: string;\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' | 'overlay' | 'fx';\n clips: Clip[];\n\n effects?: Effect[];\n duckingRules?: DuckingRule[];\n}\n\n// ────── Clip ──────\n\n// Clip-level render configuration for video/image resources\nexport interface ClipRenderConfig {\n width?: number | string; // Pixels or percentage (e.g., \"10%\")\n height?: number | string;\n}\n\ninterface BaseClip {\n id: string;\n startUs: TimeUs;\n durationUs: TimeUs;\n trackId?: string;\n\n trimStartUs?: TimeUs;\n\n effects?: Effect[];\n attachments?: Attachment[];\n\n transitionIn?: Transition;\n transitionOut?: Transition;\n\n metadata?: {\n purpose?: 'caption' | 'overlay' | 'mask';\n [key: string]: unknown;\n };\n\n // Internal: temporary field for tracking old resourceId during patch operations\n oldResourceId?: string;\n}\n\nexport interface VideoClip extends BaseClip {\n trackKind: 'video';\n resourceId: string;\n\n // Render configuration (size, positioning strategy)\n renderConfig?: ClipRenderConfig;\n\n // Audio configuration for video's original sound\n audioConfig?: {\n volume?: number; // 0.0-1.0, default 1.0\n muted?: boolean; // default false\n };\n}\n\nexport interface AudioClip extends BaseClip {\n trackKind: 'audio';\n resourceId: string;\n\n // Audio configuration\n audioConfig?: {\n volume?: number; // 0.0-1.0, default 1.0\n muted?: boolean; // default false\n };\n}\n\nexport interface CaptionClip extends BaseClip {\n trackKind: 'caption';\n text: string;\n localeCode?: string;\n fontTemplate?: string;\n fontFamily?: string;\n wordTimings?: Array<{\n text: string;\n startUs: TimeUs;\n endUs: TimeUs;\n }>;\n animation?: {\n type: 'none' | 'fade' | 'wordByWord' | 'characterKTV' | 'wordByWordFancy' | 'wordByWordSlideUp';\n [key: string]: unknown;\n };\n letterCase?: 'upper' | 'lower' | 'none';\n}\n\nexport interface FxClip extends BaseClip {\n trackKind: 'fx';\n}\n\nexport interface OverlayClip extends BaseClip {\n trackKind: 'overlay';\n resourceId: string;\n\n // Render configuration (size, positioning strategy)\n renderConfig?: ClipRenderConfig;\n\n // Optional overlay-specific config\n opacity?: number; // 0.0-1.0, default 1.0\n}\n\nexport type Clip = VideoClip | AudioClip | CaptionClip | OverlayClip | FxClip;\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\n// Animation effect (effectType: 'animation')\nexport interface AnimationEffect extends Effect {\n effectType: 'animation';\n params: {\n position: { x: number; y: number };\n keyframes: AnimationKeyframe[];\n };\n}\n\nexport interface AnimationKeyframe {\n time: number; // Relative to clip.startUs (microseconds)\n transform?: Transform2D;\n opacity?: number;\n easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';\n}\n\nexport interface Transform2D {\n x?: number; // Relative offset\n y?: number;\n scaleX?: number;\n scaleY?: number;\n rotation?: number; // Degrees\n anchorX?: number; // Rotation center x (0-1)\n anchorY?: number; // Rotation center y (0-1)\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' | 'overlay' | 'mask' | string;\n startUs: TimeUs;\n durationUs: TimeUs;\n data: Record<string, unknown>;\n}\n\nexport interface CaptionAttachmentData {\n text: string;\n localeCode?: string;\n fontTemplate?: string;\n fontFamily?: string;\n wordTimings?: Array<{\n text: string;\n startUs: TimeUs;\n endUs: TimeUs;\n }>;\n animation?: {\n type: 'none' | 'fade' | 'wordByWord' | 'characterKTV' | 'wordByWordFancy' | 'wordByWordSlideUp';\n [key: string]: unknown;\n };\n letterCase?: 'upper' | 'lower' | 'none';\n overlayClipStartUs?: TimeUs;\n mainClipStartUs?: TimeUs;\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\n// ────── Type Guards ──────\nexport function isVideoClip(clip: Clip): clip is VideoClip {\n return clip.trackKind === 'video';\n}\n\nexport function isAudioClip(clip: Clip): clip is AudioClip {\n return clip.trackKind === 'audio';\n}\n\nexport function isCaptionClip(clip: Clip): clip is CaptionClip {\n return clip.trackKind === 'caption';\n}\n\nexport function isFxClip(clip: Clip): clip is FxClip {\n return clip.trackKind === 'fx';\n}\n\nexport function hasResourceId(clip: Clip): clip is VideoClip | AudioClip {\n return isVideoClip(clip) || isAudioClip(clip);\n}\n\nexport function hasAudioConfig(clip: Clip): clip is VideoClip | AudioClip {\n return isVideoClip(clip) || isAudioClip(clip);\n}\n"],"names":[],"mappings":"
|
|
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 tracks: Track[];\n resources: Record<string, Resource>;\n\n mainTrackId?: string;\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' | 'overlay' | 'fx';\n clips: Clip[];\n\n effects?: Effect[];\n duckingRules?: DuckingRule[];\n}\n\n// ────── Clip ──────\n\n// Clip-level render configuration for video/image resources\nexport interface ClipRenderConfig {\n width?: number | string; // Pixels or percentage (e.g., \"10%\")\n height?: number | string;\n}\n\ninterface BaseClip {\n id: string;\n startUs: TimeUs;\n durationUs: TimeUs;\n trackId?: string;\n\n trimStartUs?: TimeUs;\n /**\n * Loop the underlying media content to fill this clip's duration.\n *\n * - AudioClip: loop audio samples\n * - VideoClip: (future) loop video frames (and embedded audio) together\n */\n loop?: boolean;\n\n effects?: Effect[];\n attachments?: Attachment[];\n\n transitionIn?: Transition;\n transitionOut?: Transition;\n\n metadata?: {\n purpose?: 'caption' | 'overlay' | 'mask';\n [key: string]: unknown;\n };\n\n // Internal: temporary field for tracking old resourceId during patch operations\n oldResourceId?: string;\n}\n\nexport interface VideoClip extends BaseClip {\n trackKind: 'video';\n resourceId: string;\n\n // Render configuration (size, positioning strategy)\n renderConfig?: ClipRenderConfig;\n\n // Audio configuration for video's original sound\n audioConfig?: {\n volume?: number; // 0.0-1.0, default 1.0\n muted?: boolean; // default false\n };\n}\n\nexport interface AudioClip extends BaseClip {\n trackKind: 'audio';\n resourceId: string;\n\n // Audio configuration\n audioConfig?: {\n volume?: number; // 0.0-1.0, default 1.0\n muted?: boolean; // default false\n };\n}\n\nexport interface CaptionClip extends BaseClip {\n trackKind: 'caption';\n text: string;\n localeCode?: string;\n fontTemplate?: string;\n fontFamily?: string;\n wordTimings?: Array<{\n text: string;\n startUs: TimeUs;\n endUs: TimeUs;\n }>;\n animation?: {\n type: 'none' | 'fade' | 'wordByWord' | 'characterKTV' | 'wordByWordFancy' | 'wordByWordSlideUp';\n [key: string]: unknown;\n };\n letterCase?: 'upper' | 'lower' | 'none';\n}\n\nexport interface FxClip extends BaseClip {\n trackKind: 'fx';\n}\n\nexport interface OverlayClip extends BaseClip {\n trackKind: 'overlay';\n resourceId: string;\n\n // Render configuration (size, positioning strategy)\n renderConfig?: ClipRenderConfig;\n\n // Optional overlay-specific config\n opacity?: number; // 0.0-1.0, default 1.0\n}\n\nexport type Clip = VideoClip | AudioClip | CaptionClip | OverlayClip | FxClip;\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\n// Animation effect (effectType: 'animation')\nexport interface AnimationEffect extends Effect {\n effectType: 'animation';\n params: {\n position: { x: number; y: number };\n keyframes: AnimationKeyframe[];\n };\n}\n\nexport interface AnimationKeyframe {\n time: number; // Relative to clip.startUs (microseconds)\n transform?: Transform2D;\n opacity?: number;\n easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';\n}\n\nexport interface Transform2D {\n x?: number; // Relative offset\n y?: number;\n scaleX?: number;\n scaleY?: number;\n rotation?: number; // Degrees\n anchorX?: number; // Rotation center x (0-1)\n anchorY?: number; // Rotation center y (0-1)\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' | 'overlay' | 'mask' | string;\n startUs: TimeUs;\n durationUs: TimeUs;\n data: Record<string, unknown>;\n}\n\nexport interface CaptionAttachmentData {\n text: string;\n localeCode?: string;\n fontTemplate?: string;\n fontFamily?: string;\n wordTimings?: Array<{\n text: string;\n startUs: TimeUs;\n endUs: TimeUs;\n }>;\n animation?: {\n type: 'none' | 'fade' | 'wordByWord' | 'characterKTV' | 'wordByWordFancy' | 'wordByWordSlideUp';\n [key: string]: unknown;\n };\n letterCase?: 'upper' | 'lower' | 'none';\n overlayClipStartUs?: TimeUs;\n mainClipStartUs?: TimeUs;\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\n// ────── Type Guards ──────\nexport function isVideoClip(clip: Clip): clip is VideoClip {\n return clip.trackKind === 'video';\n}\n\nexport function isAudioClip(clip: Clip): clip is AudioClip {\n return clip.trackKind === 'audio';\n}\n\nexport function isCaptionClip(clip: Clip): clip is CaptionClip {\n return clip.trackKind === 'caption';\n}\n\nexport function isFxClip(clip: Clip): clip is FxClip {\n return clip.trackKind === 'fx';\n}\n\nexport function hasResourceId(clip: Clip): clip is VideoClip | AudioClip {\n return isVideoClip(clip) || isAudioClip(clip);\n}\n\nexport function hasAudioConfig(clip: Clip): clip is VideoClip | AudioClip {\n return isVideoClip(clip) || isAudioClip(clip);\n}\n"],"names":[],"mappings":"AAyTO,SAAS,YAAY,MAA+B;AACzD,SAAO,KAAK,cAAc;AAC5B;AAEO,SAAS,YAAY,MAA+B;AACzD,SAAO,KAAK,cAAc;AAC5B;AAUO,SAAS,cAAc,MAA2C;AACvE,SAAO,YAAY,IAAI,KAAK,YAAY,IAAI;AAC9C;AAEO,SAAS,eAAe,MAA2C;AACxE,SAAO,YAAY,IAAI,KAAK,YAAY,IAAI;AAC9C;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioWindowPreparer.d.ts","sourceRoot":"","sources":["../../src/orchestrator/AudioWindowPreparer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"AudioWindowPreparer.d.ts","sourceRoot":"","sources":["../../src/orchestrator/AudioWindowPreparer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAG3C,UAAU,gBAAgB;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,uBAAuB;IAC/B,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;CACrC;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,KAAK,CAAiC;gBAElC,IAAI,EAAE,uBAAuB;IAIzC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAIvC,QAAQ,IAAI,gBAAgB,GAAG,IAAI;IAInC,KAAK,IAAI,IAAI;IAKb,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAM5C;;;;;;;OAOG;IACH,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAmB7D,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IA6CtC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASnD;;OAEG;IACG,uBAAuB,CAC3B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;QAAE,IAAI,CAAC,EAAE,WAAW,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5E,OAAO,CAAC,IAAI,CAAC;IA+FhB;;OAEG;IACG,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,OAAe,GAC1B,OAAO,CAAC,IAAI,CAAC;IAShB;;OAEG;YACW,iBAAiB;IA8E/B;;OAEG;YACW,kBAAkB;CAiEjC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MeframeEvent } from "../event/events.js";
|
|
2
2
|
import { AudioChunkDecoder } from "../stages/decode/AudioChunkDecoder.js";
|
|
3
3
|
import { hasResourceId, isAudioClip } from "../model/types.js";
|
|
4
|
+
import { buildLoopedResourceSegments } from "../utils/loop-utils.js";
|
|
4
5
|
class AudioWindowPreparer {
|
|
5
6
|
activeClips = /* @__PURE__ */ new Set();
|
|
6
7
|
deps;
|
|
@@ -118,9 +119,28 @@ class AudioWindowPreparer {
|
|
|
118
119
|
const clipRelativeStartUs = Math.max(0, startUs - clip.startUs);
|
|
119
120
|
const clipRelativeEndUs = Math.min(clip.durationUs, endUs - clip.startUs);
|
|
120
121
|
const trimStartUs = clip.trimStartUs ?? 0;
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
|
|
122
|
+
const loop = clip.trackKind === "audio" && clip.loop === true;
|
|
123
|
+
const audioRecord = this.deps.cacheManager.audioSampleCache.get(clip.resourceId);
|
|
124
|
+
const resourceDurationUs = audioRecord?.durationUs ?? 0;
|
|
125
|
+
let segments = buildLoopedResourceSegments({
|
|
126
|
+
clipRelativeStartUs,
|
|
127
|
+
clipRelativeEndUs,
|
|
128
|
+
trimStartUs,
|
|
129
|
+
resourceDurationUs,
|
|
130
|
+
loop
|
|
131
|
+
});
|
|
132
|
+
if (segments.length === 0 && clipRelativeEndUs > clipRelativeStartUs) {
|
|
133
|
+
segments = buildLoopedResourceSegments({
|
|
134
|
+
clipRelativeStartUs,
|
|
135
|
+
clipRelativeEndUs,
|
|
136
|
+
trimStartUs,
|
|
137
|
+
resourceDurationUs,
|
|
138
|
+
loop: false
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
for (const seg of segments) {
|
|
142
|
+
await this.ensureAudioWindow(clip.id, seg.resourceStartUs, seg.resourceEndUs, strictMode);
|
|
143
|
+
}
|
|
124
144
|
});
|
|
125
145
|
if (mode === "probe") {
|
|
126
146
|
void Promise.all(ensurePromises);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioWindowPreparer.js","sources":["../../src/orchestrator/AudioWindowPreparer.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\nimport type { CompositionModel } from '../model';\nimport type { ResourceLoader } from '../stages/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';\nimport { AudioChunkDecoder } from '../stages/decode/AudioChunkDecoder';\nimport { isAudioClip, hasResourceId } from '../model/types';\nimport type { RequestMode } from './types';\n\ninterface AudioDataMessage {\n sessionId: string;\n audioData: AudioData;\n clipStartUs: TimeUs;\n clipDurationUs: TimeUs;\n}\n\ninterface AudioWindowPreparerDeps {\n cacheManager: CacheManager;\n resourceLoader: ResourceLoader;\n eventBus: EventBus<EventPayloadMap>;\n}\n\nexport class AudioWindowPreparer {\n private activeClips = new Set<string>();\n private deps: AudioWindowPreparerDeps;\n private model: CompositionModel | null = null;\n\n constructor(deps: AudioWindowPreparerDeps) {\n this.deps = deps;\n }\n\n setModel(model: CompositionModel): void {\n this.model = model;\n }\n\n getModel(): CompositionModel | null {\n return this.model;\n }\n\n reset(): void {\n this.deps.cacheManager.clearAudioCache();\n this.activeClips.clear();\n }\n\n onAudioData(message: AudioDataMessage): void {\n const { sessionId, audioData, clipStartUs, clipDurationUs } = message;\n const globalTimeUs = clipStartUs + (audioData.timestamp ?? 0);\n this.deps.cacheManager.putClipAudioData(sessionId, audioData, clipDurationUs, globalTimeUs);\n }\n\n /**\n * Fast readiness probe for preview playback.\n *\n * This is intentionally synchronous and lightweight:\n * - Only checks resource-level readiness (download + MP4 index parsing).\n * - If any relevant resource isn't ready yet, return false.\n * - Does NOT require audio samples / PCM window coverage (probe is resource-level only).\n */\n isAudioResourceWindowReady(startUs: TimeUs, endUs: TimeUs): boolean {\n const model = this.model;\n if (!model) return true;\n\n const activeClips = model.getActiveClips(startUs, endUs);\n\n for (const clip of activeClips) {\n if (clip.trackKind !== 'audio' && clip.trackKind !== 'video') continue;\n if (!hasResourceId(clip)) continue;\n\n const resource = model.getResource(clip.resourceId);\n if (resource?.state !== 'ready') {\n return false;\n }\n }\n\n return true;\n }\n\n async activateAllAudioClips(): Promise<void> {\n const model = this.model;\n if (!model) {\n return;\n }\n\n const audioTracks = model.tracks.filter((track) => track.kind === 'audio');\n if (audioTracks.length === 0) return;\n\n // Find maximum clip count across all audio tracks\n const maxClipCount = Math.max(...audioTracks.map((track) => track.clips.length));\n\n // Horizontal loading: activate clip[0] from all tracks, then clip[1], etc.\n for (let clipIndex = 0; clipIndex < maxClipCount; clipIndex++) {\n for (const track of audioTracks) {\n const clip = track.clips[clipIndex];\n if (!clip || this.activeClips.has(clip.id)) continue;\n\n if (!isAudioClip(clip)) {\n throw new Error(`Clip ${clip.id} in audio track is not an audio clip`);\n }\n\n // Preview: Use main-thread parsing → AudioSampleCache → on-demand decode\n // Check if we have cached samples (already parsed in ResourceLoader)\n if (this.deps.cacheManager.audioSampleCache.has(clip.resourceId)) {\n // Already parsed, mark as active\n this.activeClips.add(clip.id);\n this.deps.eventBus.emit(MeframeEvent.ClipActivated, { clipId: clip.id });\n continue;\n }\n\n // Ensure resource is loaded (will be parsed and cached in main thread)\n await this.deps.resourceLoader.load(clip.resourceId, {\n isPreload: false,\n clipId: clip.id,\n trackId: track.id,\n });\n\n // Mark as active\n this.activeClips.add(clip.id);\n this.deps.eventBus.emit(MeframeEvent.ClipActivated, { clipId: clip.id });\n }\n }\n }\n\n async deactivateClip(clipId: string): Promise<void> {\n if (!this.activeClips.has(clipId)) {\n return;\n }\n\n this.activeClips.delete(clipId);\n this.deps.cacheManager.clearClipAudioData(clipId);\n }\n\n /**\n * Core method to ensure audio for all clips in a time range\n */\n async ensureAudioForTimeRange(\n startUs: TimeUs,\n endUs: TimeUs,\n options: { mode?: RequestMode; loadResource?: boolean; strictMode?: boolean }\n ): Promise<void> {\n const model = this.model;\n if (!model) return;\n\n const { mode = 'blocking', loadResource = true, strictMode = false } = options;\n\n // Find all clips that overlap with [startUs, endUs]\n const activeClips = model.getActiveClips(startUs, endUs);\n\n const ensurePromises = activeClips.map(async (clip) => {\n // Only process audio and video clips.\n // Note: video clips may carry an embedded audio track; preview should include them as well.\n if (clip.trackKind !== 'audio' && clip.trackKind !== 'video') return;\n if (!hasResourceId(clip)) return;\n\n // Skip muted/zero-volume clips early; they should not block preview or export.\n const muted = clip.audioConfig?.muted ?? false;\n const volume = clip.audioConfig?.volume ?? 1.0;\n if (muted || volume <= 0) {\n return;\n }\n\n // Skip clips without audio track (performance optimization)\n // If AudioSampleCache doesn't have this resource, and resource is ready,\n // it means the resource has no audio track (e.g., video-only or image)\n const resource = model.getResource(clip.resourceId);\n if (\n resource?.state === 'ready' &&\n !this.deps.cacheManager.audioSampleCache.has(clip.resourceId)\n ) {\n // Resource is ready but has no audio samples - skip\n return;\n }\n\n // Ensure AudioSampleCache has data\n if (!this.deps.cacheManager.audioSampleCache.has(clip.resourceId)) {\n if (!loadResource) {\n // Export mode: skip clips without cached samples\n return;\n }\n\n const resource = model.getResource(clip.resourceId);\n if (resource?.state !== 'ready') {\n // Resource not yet loaded - wait for it\n await this.deps.resourceLoader.load(clip.resourceId, {\n isPreload: false,\n clipId: clip.id,\n trackId: clip.trackId,\n });\n }\n }\n\n // Calculate clip-relative time range\n const clipRelativeStartUs = Math.max(0, startUs - clip.startUs);\n const clipRelativeEndUs = Math.min(clip.durationUs, endUs - clip.startUs);\n\n // Convert to resource time (aligned with video architecture)\n const trimStartUs = clip.trimStartUs ?? 0;\n const resourceStartUs = clipRelativeStartUs + trimStartUs;\n const resourceEndUs = clipRelativeEndUs + trimStartUs;\n\n // Ensure audio window using resource time coordinates\n await this.ensureAudioWindow(clip.id, resourceStartUs, resourceEndUs, strictMode);\n });\n\n if (mode === 'probe') {\n void Promise.all(ensurePromises);\n return;\n }\n await Promise.all(ensurePromises);\n }\n\n /**\n * Ensure audio window for a clip\n */\n async ensureAudioWindow(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs,\n strictMode: boolean = false\n ): Promise<void> {\n // Check L1 cache - window-level verification\n if (this.deps.cacheManager.hasWindowPCM(clipId, startUs, endUs, strictMode)) {\n return; // Entire window has sufficient data\n }\n\n await this.decodeAudioWindow(clipId, startUs, endUs);\n }\n\n /**\n * Decode audio window for a clip\n */\n private async decodeAudioWindow(clipId: string, startUs: TimeUs, endUs: TimeUs): Promise<void> {\n const clip = this.model?.findClip(clipId);\n if (!clip || !hasResourceId(clip)) {\n return;\n }\n\n // Get audio samples from AudioSampleCache\n const audioRecord = this.deps.cacheManager.audioSampleCache.get(clip.resourceId);\n if (!audioRecord) {\n // Resource has no audio track (common for some video files)\n return;\n }\n\n // Filter chunks within window (aligned with video GOP filtering)\n const windowChunks = audioRecord.samples.filter((s) => {\n const sampleEndUs = s.timestamp + (s.duration ?? 0);\n return s.timestamp < endUs && sampleEndUs > startUs;\n });\n\n if (windowChunks.length === 0) {\n return;\n }\n\n // Incremental decoding with smart threshold strategy\n const INCREMENTAL_THRESHOLD = 0.95; // 95% coverage: skip decode\n const FULL_FALLBACK_THRESHOLD = 0.3; // <30% coverage: full decode to avoid fragmentation\n\n // Check window-level coverage\n const coverage = this.deps.cacheManager.getAudioRangeCoverage(\n clipId,\n startUs,\n endUs,\n INCREMENTAL_THRESHOLD\n );\n\n // Strategy 1: High coverage - skip decode entirely\n if (coverage.covered) {\n return;\n }\n\n // Strategy 2: Very low coverage - full decode (avoid fragmentation overhead)\n if (coverage.coverageRatio < FULL_FALLBACK_THRESHOLD) {\n await this.decodeAudioSamples(\n clipId,\n windowChunks,\n audioRecord.metadata,\n clip.durationUs,\n clip.startUs\n );\n return;\n }\n\n // Strategy 3: Medium coverage - incremental decode (30%-95%)\n const chunksToDecode = windowChunks.filter((chunk) => {\n const chunkEndUs = chunk.timestamp + (chunk.duration ?? 0);\n const chunkCoverage = this.deps.cacheManager.getAudioRangeCoverage(\n clipId,\n chunk.timestamp,\n chunkEndUs,\n 0.95\n );\n return !chunkCoverage.covered;\n });\n\n if (chunksToDecode.length === 0) {\n return;\n }\n\n // Decode only missing chunks\n await this.decodeAudioSamples(\n clipId,\n chunksToDecode,\n audioRecord.metadata,\n clip.durationUs,\n clip.startUs\n );\n }\n\n /**\n * Decode audio samples to PCM and cache\n */\n private async decodeAudioSamples(\n clipId: string,\n samples: EncodedAudioChunk[],\n config: AudioDecoderConfig,\n clipDurationUs: number,\n clipStartUs: TimeUs\n ): Promise<void> {\n // Convert description to ArrayBuffer if needed for type compatibility\n let description: ArrayBuffer | undefined;\n if (config.description) {\n if (config.description instanceof ArrayBuffer) {\n description = config.description;\n } else if (ArrayBuffer.isView(config.description)) {\n const view = config.description as Uint8Array;\n const newBuffer = new ArrayBuffer(view.byteLength);\n new Uint8Array(newBuffer).set(\n new Uint8Array(view.buffer, view.byteOffset, view.byteLength)\n );\n description = newBuffer;\n }\n }\n\n const decoderConfig = {\n codec: config.codec,\n sampleRate: config.sampleRate,\n numberOfChannels: config.numberOfChannels,\n description,\n };\n const decoder = new AudioChunkDecoder(`audio-${clipId}`, decoderConfig);\n\n try {\n // Create chunk stream\n const chunkStream = new ReadableStream<EncodedAudioChunk>({\n start(controller) {\n for (const sample of samples) {\n controller.enqueue(sample);\n }\n controller.close();\n },\n });\n\n // Decode through stream\n const audioDataStream = chunkStream.pipeThrough(decoder.createStream());\n const reader = audioDataStream.getReader();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n if (value) {\n const globalTimeUs = clipStartUs + (value.timestamp ?? 0);\n this.deps.cacheManager.putClipAudioData(clipId, value, clipDurationUs, globalTimeUs);\n }\n }\n } finally {\n reader.releaseLock();\n }\n } catch (error) {\n console.error(`[AudioWindowPreparer] Decoder error for clip ${clipId}:`, error);\n throw error;\n } finally {\n await decoder.close();\n }\n }\n}\n"],"names":["resource"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AudioWindowPreparer.js","sources":["../../src/orchestrator/AudioWindowPreparer.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\nimport type { CompositionModel } from '../model';\nimport type { ResourceLoader } from '../stages/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';\nimport { AudioChunkDecoder } from '../stages/decode/AudioChunkDecoder';\nimport { isAudioClip, hasResourceId } from '../model/types';\nimport type { RequestMode } from './types';\nimport { buildLoopedResourceSegments } from '../utils/loop-utils';\n\ninterface AudioDataMessage {\n sessionId: string;\n audioData: AudioData;\n clipStartUs: TimeUs;\n clipDurationUs: TimeUs;\n}\n\ninterface AudioWindowPreparerDeps {\n cacheManager: CacheManager;\n resourceLoader: ResourceLoader;\n eventBus: EventBus<EventPayloadMap>;\n}\n\nexport class AudioWindowPreparer {\n private activeClips = new Set<string>();\n private deps: AudioWindowPreparerDeps;\n private model: CompositionModel | null = null;\n\n constructor(deps: AudioWindowPreparerDeps) {\n this.deps = deps;\n }\n\n setModel(model: CompositionModel): void {\n this.model = model;\n }\n\n getModel(): CompositionModel | null {\n return this.model;\n }\n\n reset(): void {\n this.deps.cacheManager.clearAudioCache();\n this.activeClips.clear();\n }\n\n onAudioData(message: AudioDataMessage): void {\n const { sessionId, audioData, clipStartUs, clipDurationUs } = message;\n const globalTimeUs = clipStartUs + (audioData.timestamp ?? 0);\n this.deps.cacheManager.putClipAudioData(sessionId, audioData, clipDurationUs, globalTimeUs);\n }\n\n /**\n * Fast readiness probe for preview playback.\n *\n * This is intentionally synchronous and lightweight:\n * - Only checks resource-level readiness (download + MP4 index parsing).\n * - If any relevant resource isn't ready yet, return false.\n * - Does NOT require audio samples / PCM window coverage (probe is resource-level only).\n */\n isAudioResourceWindowReady(startUs: TimeUs, endUs: TimeUs): boolean {\n const model = this.model;\n if (!model) return true;\n\n const activeClips = model.getActiveClips(startUs, endUs);\n\n for (const clip of activeClips) {\n if (clip.trackKind !== 'audio' && clip.trackKind !== 'video') continue;\n if (!hasResourceId(clip)) continue;\n\n const resource = model.getResource(clip.resourceId);\n if (resource?.state !== 'ready') {\n return false;\n }\n }\n\n return true;\n }\n\n async activateAllAudioClips(): Promise<void> {\n const model = this.model;\n if (!model) {\n return;\n }\n\n const audioTracks = model.tracks.filter((track) => track.kind === 'audio');\n if (audioTracks.length === 0) return;\n\n // Find maximum clip count across all audio tracks\n const maxClipCount = Math.max(...audioTracks.map((track) => track.clips.length));\n\n // Horizontal loading: activate clip[0] from all tracks, then clip[1], etc.\n for (let clipIndex = 0; clipIndex < maxClipCount; clipIndex++) {\n for (const track of audioTracks) {\n const clip = track.clips[clipIndex];\n if (!clip || this.activeClips.has(clip.id)) continue;\n\n if (!isAudioClip(clip)) {\n throw new Error(`Clip ${clip.id} in audio track is not an audio clip`);\n }\n\n // Preview: Use main-thread parsing → AudioSampleCache → on-demand decode\n // Check if we have cached samples (already parsed in ResourceLoader)\n if (this.deps.cacheManager.audioSampleCache.has(clip.resourceId)) {\n // Already parsed, mark as active\n this.activeClips.add(clip.id);\n this.deps.eventBus.emit(MeframeEvent.ClipActivated, { clipId: clip.id });\n continue;\n }\n\n // Ensure resource is loaded (will be parsed and cached in main thread)\n await this.deps.resourceLoader.load(clip.resourceId, {\n isPreload: false,\n clipId: clip.id,\n trackId: track.id,\n });\n\n // Mark as active\n this.activeClips.add(clip.id);\n this.deps.eventBus.emit(MeframeEvent.ClipActivated, { clipId: clip.id });\n }\n }\n }\n\n async deactivateClip(clipId: string): Promise<void> {\n if (!this.activeClips.has(clipId)) {\n return;\n }\n\n this.activeClips.delete(clipId);\n this.deps.cacheManager.clearClipAudioData(clipId);\n }\n\n /**\n * Core method to ensure audio for all clips in a time range\n */\n async ensureAudioForTimeRange(\n startUs: TimeUs,\n endUs: TimeUs,\n options: { mode?: RequestMode; loadResource?: boolean; strictMode?: boolean }\n ): Promise<void> {\n const model = this.model;\n if (!model) return;\n\n const { mode = 'blocking', loadResource = true, strictMode = false } = options;\n\n // Find all clips that overlap with [startUs, endUs]\n const activeClips = model.getActiveClips(startUs, endUs);\n\n const ensurePromises = activeClips.map(async (clip) => {\n // Only process audio and video clips.\n // Note: video clips may carry an embedded audio track; preview should include them as well.\n if (clip.trackKind !== 'audio' && clip.trackKind !== 'video') return;\n if (!hasResourceId(clip)) return;\n\n // Skip muted/zero-volume clips early; they should not block preview or export.\n const muted = clip.audioConfig?.muted ?? false;\n const volume = clip.audioConfig?.volume ?? 1.0;\n if (muted || volume <= 0) {\n return;\n }\n\n // Skip clips without audio track (performance optimization)\n // If AudioSampleCache doesn't have this resource, and resource is ready,\n // it means the resource has no audio track (e.g., video-only or image)\n const resource = model.getResource(clip.resourceId);\n if (\n resource?.state === 'ready' &&\n !this.deps.cacheManager.audioSampleCache.has(clip.resourceId)\n ) {\n // Resource is ready but has no audio samples - skip\n return;\n }\n\n // Ensure AudioSampleCache has data\n if (!this.deps.cacheManager.audioSampleCache.has(clip.resourceId)) {\n if (!loadResource) {\n // Export mode: skip clips without cached samples\n return;\n }\n\n const resource = model.getResource(clip.resourceId);\n if (resource?.state !== 'ready') {\n // Resource not yet loaded - wait for it\n await this.deps.resourceLoader.load(clip.resourceId, {\n isPreload: false,\n clipId: clip.id,\n trackId: clip.trackId,\n });\n }\n }\n\n // Calculate clip-relative time range\n const clipRelativeStartUs = Math.max(0, startUs - clip.startUs);\n const clipRelativeEndUs = Math.min(clip.durationUs, endUs - clip.startUs);\n\n // Convert to resource time (aligned with video architecture)\n const trimStartUs = clip.trimStartUs ?? 0;\n const loop = clip.trackKind === 'audio' && clip.loop === true;\n const audioRecord = this.deps.cacheManager.audioSampleCache.get(clip.resourceId) as {\n durationUs: TimeUs;\n } | null;\n const resourceDurationUs = audioRecord?.durationUs ?? 0;\n\n let segments = buildLoopedResourceSegments({\n clipRelativeStartUs,\n clipRelativeEndUs,\n trimStartUs,\n resourceDurationUs,\n loop,\n });\n if (segments.length === 0 && clipRelativeEndUs > clipRelativeStartUs) {\n // Fallback to non-loop mapping when resource duration isn't available yet.\n segments = buildLoopedResourceSegments({\n clipRelativeStartUs,\n clipRelativeEndUs,\n trimStartUs,\n resourceDurationUs,\n loop: false,\n });\n }\n\n // Ensure audio window using resource time coordinates\n for (const seg of segments) {\n await this.ensureAudioWindow(clip.id, seg.resourceStartUs, seg.resourceEndUs, strictMode);\n }\n });\n\n if (mode === 'probe') {\n void Promise.all(ensurePromises);\n return;\n }\n await Promise.all(ensurePromises);\n }\n\n /**\n * Ensure audio window for a clip\n */\n async ensureAudioWindow(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs,\n strictMode: boolean = false\n ): Promise<void> {\n // Check L1 cache - window-level verification\n if (this.deps.cacheManager.hasWindowPCM(clipId, startUs, endUs, strictMode)) {\n return; // Entire window has sufficient data\n }\n\n await this.decodeAudioWindow(clipId, startUs, endUs);\n }\n\n /**\n * Decode audio window for a clip\n */\n private async decodeAudioWindow(clipId: string, startUs: TimeUs, endUs: TimeUs): Promise<void> {\n const clip = this.model?.findClip(clipId);\n if (!clip || !hasResourceId(clip)) {\n return;\n }\n\n // Get audio samples from AudioSampleCache\n const audioRecord = this.deps.cacheManager.audioSampleCache.get(clip.resourceId);\n if (!audioRecord) {\n // Resource has no audio track (common for some video files)\n return;\n }\n\n // Filter chunks within window (aligned with video GOP filtering)\n const windowChunks = audioRecord.samples.filter((s) => {\n const sampleEndUs = s.timestamp + (s.duration ?? 0);\n return s.timestamp < endUs && sampleEndUs > startUs;\n });\n\n if (windowChunks.length === 0) {\n return;\n }\n\n // Incremental decoding with smart threshold strategy\n const INCREMENTAL_THRESHOLD = 0.95; // 95% coverage: skip decode\n const FULL_FALLBACK_THRESHOLD = 0.3; // <30% coverage: full decode to avoid fragmentation\n\n // Check window-level coverage\n const coverage = this.deps.cacheManager.getAudioRangeCoverage(\n clipId,\n startUs,\n endUs,\n INCREMENTAL_THRESHOLD\n );\n\n // Strategy 1: High coverage - skip decode entirely\n if (coverage.covered) {\n return;\n }\n\n // Strategy 2: Very low coverage - full decode (avoid fragmentation overhead)\n if (coverage.coverageRatio < FULL_FALLBACK_THRESHOLD) {\n await this.decodeAudioSamples(\n clipId,\n windowChunks,\n audioRecord.metadata,\n clip.durationUs,\n clip.startUs\n );\n return;\n }\n\n // Strategy 3: Medium coverage - incremental decode (30%-95%)\n const chunksToDecode = windowChunks.filter((chunk) => {\n const chunkEndUs = chunk.timestamp + (chunk.duration ?? 0);\n const chunkCoverage = this.deps.cacheManager.getAudioRangeCoverage(\n clipId,\n chunk.timestamp,\n chunkEndUs,\n 0.95\n );\n return !chunkCoverage.covered;\n });\n\n if (chunksToDecode.length === 0) {\n return;\n }\n\n // Decode only missing chunks\n await this.decodeAudioSamples(\n clipId,\n chunksToDecode,\n audioRecord.metadata,\n clip.durationUs,\n clip.startUs\n );\n }\n\n /**\n * Decode audio samples to PCM and cache\n */\n private async decodeAudioSamples(\n clipId: string,\n samples: EncodedAudioChunk[],\n config: AudioDecoderConfig,\n clipDurationUs: number,\n clipStartUs: TimeUs\n ): Promise<void> {\n // Convert description to ArrayBuffer if needed for type compatibility\n let description: ArrayBuffer | undefined;\n if (config.description) {\n if (config.description instanceof ArrayBuffer) {\n description = config.description;\n } else if (ArrayBuffer.isView(config.description)) {\n const view = config.description as Uint8Array;\n const newBuffer = new ArrayBuffer(view.byteLength);\n new Uint8Array(newBuffer).set(\n new Uint8Array(view.buffer, view.byteOffset, view.byteLength)\n );\n description = newBuffer;\n }\n }\n\n const decoderConfig = {\n codec: config.codec,\n sampleRate: config.sampleRate,\n numberOfChannels: config.numberOfChannels,\n description,\n };\n const decoder = new AudioChunkDecoder(`audio-${clipId}`, decoderConfig);\n\n try {\n // Create chunk stream\n const chunkStream = new ReadableStream<EncodedAudioChunk>({\n start(controller) {\n for (const sample of samples) {\n controller.enqueue(sample);\n }\n controller.close();\n },\n });\n\n // Decode through stream\n const audioDataStream = chunkStream.pipeThrough(decoder.createStream());\n const reader = audioDataStream.getReader();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n if (value) {\n const globalTimeUs = clipStartUs + (value.timestamp ?? 0);\n this.deps.cacheManager.putClipAudioData(clipId, value, clipDurationUs, globalTimeUs);\n }\n }\n } finally {\n reader.releaseLock();\n }\n } catch (error) {\n console.error(`[AudioWindowPreparer] Decoder error for clip ${clipId}:`, error);\n throw error;\n } finally {\n await decoder.close();\n }\n }\n}\n"],"names":["resource"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OfflineAudioMixer.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/OfflineAudioMixer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"OfflineAudioMixer.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/OfflineAudioMixer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAU7D,qBAAa,iBAAiB;IAK1B,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,QAAQ;IALlB,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,gBAAgB,CAAK;gBAGnB,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,MAAM,gBAAgB,GAAG,IAAI;IAG3C,GAAG,CAAC,aAAa,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAyG3E,OAAO,CAAC,gBAAgB;CA2CzB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { hasAudioConfig } from "../../model/types.js";
|
|
1
|
+
import { hasResourceId, hasAudioConfig } from "../../model/types.js";
|
|
2
|
+
import { buildLoopedResourceSegments } from "../../utils/loop-utils.js";
|
|
2
3
|
class OfflineAudioMixer {
|
|
3
4
|
constructor(cacheManager, getModel) {
|
|
4
5
|
this.cacheManager = cacheManager;
|
|
@@ -29,36 +30,54 @@ class OfflineAudioMixer {
|
|
|
29
30
|
const clipRelativeEndUs = clipIntersectEndUs - clip.startUs;
|
|
30
31
|
const clipModel = this.getModel()?.findClip(clip.clipId);
|
|
31
32
|
const trimStartUs = clipModel?.trimStartUs ?? 0;
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
const loop = clipModel?.trackKind === "audio" && clipModel.loop === true;
|
|
34
|
+
const resourceDurationUs = clipModel && hasResourceId(clipModel) ? this.cacheManager.audioSampleCache.get(clipModel.resourceId)?.durationUs ?? 0 : 0;
|
|
35
|
+
let segments = buildLoopedResourceSegments({
|
|
36
|
+
clipRelativeStartUs,
|
|
37
|
+
clipRelativeEndUs,
|
|
38
|
+
trimStartUs,
|
|
39
|
+
resourceDurationUs,
|
|
40
|
+
loop
|
|
41
|
+
});
|
|
42
|
+
if (segments.length === 0 && clipRelativeEndUs > clipRelativeStartUs) {
|
|
43
|
+
segments = buildLoopedResourceSegments({
|
|
44
|
+
clipRelativeStartUs,
|
|
45
|
+
clipRelativeEndUs,
|
|
46
|
+
trimStartUs,
|
|
47
|
+
resourceDurationUs,
|
|
48
|
+
loop: false
|
|
49
|
+
});
|
|
41
50
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
for (const seg of segments) {
|
|
52
|
+
const pcmData = this.cacheManager.getClipPCMWithMetadata(
|
|
53
|
+
clip.clipId,
|
|
54
|
+
seg.resourceStartUs,
|
|
55
|
+
seg.resourceEndUs
|
|
56
|
+
);
|
|
57
|
+
if (!pcmData || pcmData.planes.length === 0) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const intersectFrames = pcmData.planes[0]?.length ?? 0;
|
|
61
|
+
if (intersectFrames === 0) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const buffer = ctx.createBuffer(pcmData.planes.length, intersectFrames, pcmData.sampleRate);
|
|
65
|
+
for (let channel = 0; channel < pcmData.planes.length; channel++) {
|
|
66
|
+
const plane = pcmData.planes[channel];
|
|
67
|
+
if (plane) {
|
|
68
|
+
buffer.copyToChannel(new Float32Array(plane), channel);
|
|
69
|
+
}
|
|
51
70
|
}
|
|
71
|
+
const source = ctx.createBufferSource();
|
|
72
|
+
source.buffer = buffer;
|
|
73
|
+
const gainNode = ctx.createGain();
|
|
74
|
+
gainNode.gain.value = clip.volume;
|
|
75
|
+
source.connect(gainNode);
|
|
76
|
+
gainNode.connect(ctx.destination);
|
|
77
|
+
const segmentStartUs = clip.startUs + seg.clipRelativeStartUs;
|
|
78
|
+
const startTime = (segmentStartUs - windowStartUs) / 1e6;
|
|
79
|
+
source.start(startTime);
|
|
52
80
|
}
|
|
53
|
-
const source = ctx.createBufferSource();
|
|
54
|
-
source.buffer = buffer;
|
|
55
|
-
const gainNode = ctx.createGain();
|
|
56
|
-
gainNode.gain.value = clip.volume;
|
|
57
|
-
source.connect(gainNode);
|
|
58
|
-
gainNode.connect(ctx.destination);
|
|
59
|
-
const relativeStartUs = clipIntersectStartUs - windowStartUs;
|
|
60
|
-
const startTime = relativeStartUs / 1e6;
|
|
61
|
-
source.start(startTime);
|
|
62
81
|
}
|
|
63
82
|
const mixedBuffer = await ctx.startRendering();
|
|
64
83
|
return mixedBuffer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OfflineAudioMixer.js","sources":["../../../src/stages/compose/OfflineAudioMixer.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport { hasAudioConfig } from '../../model/types';\nimport type { CompositionModel } from '../../model';\nimport type { CacheManager } from '../../cache/CacheManager';\n\ninterface MixClipInfo {\n clipId: string;\n startUs: TimeUs;\n durationUs: TimeUs;\n volume: number;\n}\n\nexport class OfflineAudioMixer {\n private sampleRate = 48_000;\n private numberOfChannels = 2;\n\n constructor(\n private cacheManager: CacheManager,\n private getModel: () => CompositionModel | null\n ) {}\n\n async mix(windowStartUs: TimeUs, windowEndUs: TimeUs): Promise<AudioBuffer> {\n const durationUs = windowEndUs - windowStartUs;\n // Guard against invalid/empty ranges (can happen near timeline end or after clamping).\n // OfflineAudioContext requires length >= 1.\n const frameCount = Math.max(\n 1,\n Math.ceil((Math.max(0, durationUs) / 1_000_000) * this.sampleRate)\n );\n\n const ctx = new OfflineAudioContext(this.numberOfChannels, frameCount, this.sampleRate);\n\n // Ensure the OfflineAudioContext renders the full requested length.\n // Some implementations may stop early if no sources are scheduled near the tail,\n // which would truncate trailing silence and make export audio shorter than video.\n const silent = ctx.createBuffer(1, frameCount, this.sampleRate);\n const silentSource = ctx.createBufferSource();\n silentSource.buffer = silent;\n const silentGain = ctx.createGain();\n silentGain.gain.value = 0;\n silentSource.connect(silentGain);\n silentGain.connect(ctx.destination);\n silentSource.start(0);\n\n const clips = this.getClipsInWindow(windowStartUs, windowEndUs);\n\n for (const clip of clips) {\n // Calculate clip-relative time range\n const clipIntersectStartUs = Math.max(windowStartUs, clip.startUs);\n const clipIntersectEndUs = Math.min(windowEndUs, clip.startUs + clip.durationUs);\n const clipRelativeStartUs = clipIntersectStartUs - clip.startUs;\n const clipRelativeEndUs = clipIntersectEndUs - clip.startUs;\n\n // Convert to resource time (aligned with video architecture)\n const clipModel = this.getModel()?.findClip(clip.clipId);\n const trimStartUs = clipModel?.trimStartUs ?? 0;\n const
|
|
1
|
+
{"version":3,"file":"OfflineAudioMixer.js","sources":["../../../src/stages/compose/OfflineAudioMixer.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport { hasAudioConfig, hasResourceId } from '../../model/types';\nimport type { CompositionModel } from '../../model';\nimport type { CacheManager } from '../../cache/CacheManager';\nimport { buildLoopedResourceSegments } from '../../utils/loop-utils';\n\ninterface MixClipInfo {\n clipId: string;\n startUs: TimeUs;\n durationUs: TimeUs;\n volume: number;\n}\n\nexport class OfflineAudioMixer {\n private sampleRate = 48_000;\n private numberOfChannels = 2;\n\n constructor(\n private cacheManager: CacheManager,\n private getModel: () => CompositionModel | null\n ) {}\n\n async mix(windowStartUs: TimeUs, windowEndUs: TimeUs): Promise<AudioBuffer> {\n const durationUs = windowEndUs - windowStartUs;\n // Guard against invalid/empty ranges (can happen near timeline end or after clamping).\n // OfflineAudioContext requires length >= 1.\n const frameCount = Math.max(\n 1,\n Math.ceil((Math.max(0, durationUs) / 1_000_000) * this.sampleRate)\n );\n\n const ctx = new OfflineAudioContext(this.numberOfChannels, frameCount, this.sampleRate);\n\n // Ensure the OfflineAudioContext renders the full requested length.\n // Some implementations may stop early if no sources are scheduled near the tail,\n // which would truncate trailing silence and make export audio shorter than video.\n const silent = ctx.createBuffer(1, frameCount, this.sampleRate);\n const silentSource = ctx.createBufferSource();\n silentSource.buffer = silent;\n const silentGain = ctx.createGain();\n silentGain.gain.value = 0;\n silentSource.connect(silentGain);\n silentGain.connect(ctx.destination);\n silentSource.start(0);\n\n const clips = this.getClipsInWindow(windowStartUs, windowEndUs);\n\n for (const clip of clips) {\n // Calculate clip-relative time range\n const clipIntersectStartUs = Math.max(windowStartUs, clip.startUs);\n const clipIntersectEndUs = Math.min(windowEndUs, clip.startUs + clip.durationUs);\n const clipRelativeStartUs = clipIntersectStartUs - clip.startUs;\n const clipRelativeEndUs = clipIntersectEndUs - clip.startUs;\n\n // Convert to resource time (aligned with video architecture)\n const clipModel = this.getModel()?.findClip(clip.clipId);\n const trimStartUs = clipModel?.trimStartUs ?? 0;\n const loop = clipModel?.trackKind === 'audio' && clipModel.loop === true;\n const resourceDurationUs =\n clipModel && hasResourceId(clipModel)\n ? (this.cacheManager.audioSampleCache.get(clipModel.resourceId)?.durationUs ?? 0)\n : 0;\n\n let segments = buildLoopedResourceSegments({\n clipRelativeStartUs,\n clipRelativeEndUs,\n trimStartUs,\n resourceDurationUs,\n loop,\n });\n if (segments.length === 0 && clipRelativeEndUs > clipRelativeStartUs) {\n segments = buildLoopedResourceSegments({\n clipRelativeStartUs,\n clipRelativeEndUs,\n trimStartUs,\n resourceDurationUs,\n loop: false,\n });\n }\n\n for (const seg of segments) {\n // Get PCM data using resource time coordinates\n const pcmData = this.cacheManager.getClipPCMWithMetadata(\n clip.clipId,\n seg.resourceStartUs,\n seg.resourceEndUs\n );\n\n if (!pcmData || pcmData.planes.length === 0) {\n continue;\n }\n\n const intersectFrames = pcmData.planes[0]?.length ?? 0;\n if (intersectFrames === 0) {\n continue;\n }\n\n // Create AudioBuffer\n const buffer = ctx.createBuffer(pcmData.planes.length, intersectFrames, pcmData.sampleRate);\n\n for (let channel = 0; channel < pcmData.planes.length; channel++) {\n const plane = pcmData.planes[channel];\n if (plane) {\n // Create new Float32Array to ensure correct type (ArrayBuffer, not SharedArrayBuffer)\n buffer.copyToChannel(new Float32Array(plane), channel);\n }\n }\n\n const source = ctx.createBufferSource();\n source.buffer = buffer;\n\n const gainNode = ctx.createGain();\n gainNode.gain.value = clip.volume;\n\n source.connect(gainNode);\n gainNode.connect(ctx.destination);\n\n const segmentStartUs = clip.startUs + seg.clipRelativeStartUs;\n const startTime = (segmentStartUs - windowStartUs) / 1_000_000;\n source.start(startTime);\n }\n }\n\n const mixedBuffer = await ctx.startRendering();\n return mixedBuffer;\n }\n\n private getClipsInWindow(windowStartUs: TimeUs, windowEndUs: TimeUs): MixClipInfo[] {\n const clips: MixClipInfo[] = [];\n const model = this.getModel();\n if (!model) {\n return clips;\n }\n\n for (const track of model.tracks) {\n for (const clip of track.clips) {\n const clipEndUs = clip.startUs + clip.durationUs;\n if (clip.startUs < windowEndUs && clipEndUs > windowStartUs) {\n // Read audio config (only video/audio clips have audioConfig)\n if (hasAudioConfig(clip)) {\n const muted = clip.audioConfig?.muted ?? false;\n\n // Skip muted clips in export (performance optimization)\n if (muted) {\n continue;\n }\n\n const volume = clip.audioConfig?.volume ?? 1.0;\n\n clips.push({\n clipId: clip.id,\n startUs: clip.startUs,\n durationUs: clip.durationUs,\n volume,\n });\n } else {\n // Caption/Fx clips in audio track should not happen, but handle gracefully\n clips.push({\n clipId: clip.id,\n startUs: clip.startUs,\n durationUs: clip.durationUs,\n volume: 1.0,\n });\n }\n }\n }\n }\n\n return clips;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StreamTarget, ArrayBufferTarget, Muxer } from "../../node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js";
|
|
1
|
+
import { StreamTarget, ArrayBufferTarget, Muxer } from "../../medeo-fe/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js";
|
|
2
2
|
class MP4Muxer {
|
|
3
3
|
muxer;
|
|
4
4
|
firstVideoChunk = true;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TimeUs } from '../model/types';
|
|
2
|
+
|
|
3
|
+
export interface LoopSegment {
|
|
4
|
+
clipRelativeStartUs: TimeUs;
|
|
5
|
+
durationUs: TimeUs;
|
|
6
|
+
resourceStartUs: TimeUs;
|
|
7
|
+
resourceEndUs: TimeUs;
|
|
8
|
+
}
|
|
9
|
+
export declare function buildLoopedResourceSegments(params: {
|
|
10
|
+
clipRelativeStartUs: TimeUs;
|
|
11
|
+
clipRelativeEndUs: TimeUs;
|
|
12
|
+
trimStartUs: TimeUs;
|
|
13
|
+
resourceDurationUs: TimeUs;
|
|
14
|
+
loop: boolean;
|
|
15
|
+
}): LoopSegment[];
|
|
16
|
+
//# sourceMappingURL=loop-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop-utils.d.ts","sourceRoot":"","sources":["../../src/utils/loop-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,WAAW,WAAW;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE;IAClD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,WAAW,EAAE,CA+ChB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function buildLoopedResourceSegments(params) {
|
|
2
|
+
const rangeStartUs = Math.max(0, params.clipRelativeStartUs);
|
|
3
|
+
const rangeEndUs = Math.max(rangeStartUs, params.clipRelativeEndUs);
|
|
4
|
+
const requestedDurationUs = rangeEndUs - rangeStartUs;
|
|
5
|
+
if (requestedDurationUs <= 0) {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
if (!params.loop) {
|
|
9
|
+
return [
|
|
10
|
+
{
|
|
11
|
+
clipRelativeStartUs: rangeStartUs,
|
|
12
|
+
durationUs: requestedDurationUs,
|
|
13
|
+
resourceStartUs: rangeStartUs + (params.trimStartUs ?? 0),
|
|
14
|
+
resourceEndUs: rangeEndUs + (params.trimStartUs ?? 0)
|
|
15
|
+
}
|
|
16
|
+
];
|
|
17
|
+
}
|
|
18
|
+
const trimStartUs = params.trimStartUs ?? 0;
|
|
19
|
+
const periodUs = params.resourceDurationUs - trimStartUs;
|
|
20
|
+
if (periodUs <= 0) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
const segments = [];
|
|
24
|
+
let tUs = rangeStartUs;
|
|
25
|
+
while (tUs < rangeEndUs) {
|
|
26
|
+
const offsetInPeriodUs = tUs % periodUs;
|
|
27
|
+
const maxLenUs = periodUs - offsetInPeriodUs;
|
|
28
|
+
const lenUs = Math.min(rangeEndUs - tUs, maxLenUs);
|
|
29
|
+
if (lenUs <= 0) break;
|
|
30
|
+
const resourceStartUs = trimStartUs + offsetInPeriodUs;
|
|
31
|
+
segments.push({
|
|
32
|
+
clipRelativeStartUs: tUs,
|
|
33
|
+
durationUs: lenUs,
|
|
34
|
+
resourceStartUs,
|
|
35
|
+
resourceEndUs: resourceStartUs + lenUs
|
|
36
|
+
});
|
|
37
|
+
tUs += lenUs;
|
|
38
|
+
}
|
|
39
|
+
return segments;
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
buildLoopedResourceSegments
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=loop-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop-utils.js","sources":["../../src/utils/loop-utils.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\n\nexport interface LoopSegment {\n clipRelativeStartUs: TimeUs;\n durationUs: TimeUs;\n resourceStartUs: TimeUs;\n resourceEndUs: TimeUs;\n}\n\nexport function buildLoopedResourceSegments(params: {\n clipRelativeStartUs: TimeUs;\n clipRelativeEndUs: TimeUs;\n trimStartUs: TimeUs;\n resourceDurationUs: TimeUs;\n loop: boolean;\n}): LoopSegment[] {\n const rangeStartUs = Math.max(0, params.clipRelativeStartUs);\n const rangeEndUs = Math.max(rangeStartUs, params.clipRelativeEndUs);\n\n const requestedDurationUs = rangeEndUs - rangeStartUs;\n if (requestedDurationUs <= 0) {\n return [];\n }\n\n if (!params.loop) {\n return [\n {\n clipRelativeStartUs: rangeStartUs,\n durationUs: requestedDurationUs,\n resourceStartUs: rangeStartUs + (params.trimStartUs ?? 0),\n resourceEndUs: rangeEndUs + (params.trimStartUs ?? 0),\n },\n ];\n }\n\n const trimStartUs = params.trimStartUs ?? 0;\n const periodUs = params.resourceDurationUs - trimStartUs;\n if (periodUs <= 0) {\n return [];\n }\n\n const segments: LoopSegment[] = [];\n let tUs = rangeStartUs;\n\n while (tUs < rangeEndUs) {\n const offsetInPeriodUs = tUs % periodUs;\n const maxLenUs = periodUs - offsetInPeriodUs;\n const lenUs = Math.min(rangeEndUs - tUs, maxLenUs);\n if (lenUs <= 0) break;\n\n const resourceStartUs = trimStartUs + offsetInPeriodUs;\n segments.push({\n clipRelativeStartUs: tUs,\n durationUs: lenUs,\n resourceStartUs,\n resourceEndUs: resourceStartUs + lenUs,\n });\n\n tUs += lenUs;\n }\n\n return segments;\n}\n"],"names":[],"mappings":"AASO,SAAS,4BAA4B,QAM1B;AAChB,QAAM,eAAe,KAAK,IAAI,GAAG,OAAO,mBAAmB;AAC3D,QAAM,aAAa,KAAK,IAAI,cAAc,OAAO,iBAAiB;AAElE,QAAM,sBAAsB,aAAa;AACzC,MAAI,uBAAuB,GAAG;AAC5B,WAAO,CAAA;AAAA,EACT;AAEA,MAAI,CAAC,OAAO,MAAM;AAChB,WAAO;AAAA,MACL;AAAA,QACE,qBAAqB;AAAA,QACrB,YAAY;AAAA,QACZ,iBAAiB,gBAAgB,OAAO,eAAe;AAAA,QACvD,eAAe,cAAc,OAAO,eAAe;AAAA,MAAA;AAAA,IACrD;AAAA,EAEJ;AAEA,QAAM,cAAc,OAAO,eAAe;AAC1C,QAAM,WAAW,OAAO,qBAAqB;AAC7C,MAAI,YAAY,GAAG;AACjB,WAAO,CAAA;AAAA,EACT;AAEA,QAAM,WAA0B,CAAA;AAChC,MAAI,MAAM;AAEV,SAAO,MAAM,YAAY;AACvB,UAAM,mBAAmB,MAAM;AAC/B,UAAM,WAAW,WAAW;AAC5B,UAAM,QAAQ,KAAK,IAAI,aAAa,KAAK,QAAQ;AACjD,QAAI,SAAS,EAAG;AAEhB,UAAM,kBAAkB,cAAc;AACtC,aAAS,KAAK;AAAA,MACZ,qBAAqB;AAAA,MACrB,YAAY;AAAA,MACZ;AAAA,MACA,eAAe,kBAAkB;AAAA,IAAA,CAClC;AAED,WAAO;AAAA,EACT;AAEA,SAAO;AACT;"}
|
package/dist/utils/mp4box.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as mp4box_all from "../node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js";
|
|
1
|
+
import * as mp4box_all from "../medeo-fe/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js";
|
|
2
2
|
const lib = mp4box_all;
|
|
3
3
|
const MP4Box = lib.default && typeof lib.default.createFile === "function" ? lib.default : lib;
|
|
4
4
|
if (typeof MP4Box.createFile !== "function") {
|