@linker-design-plus/timeline-track 2.1.5 → 2.2.0

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.
Files changed (28) hide show
  1. package/README.md +1 -1
  2. package/dist/core/controllers/domPreviewBackend.d.ts +2 -2
  3. package/dist/core/controllers/index.d.ts +2 -1
  4. package/dist/core/controllers/previewBackend.d.ts +4 -0
  5. package/dist/core/controllers/previewTransformMath.d.ts +6 -0
  6. package/dist/core/controllers/previewTransformOverlay.d.ts +21 -0
  7. package/dist/core/facade/timelineManager.d.ts +1 -0
  8. package/dist/core/models/types.d.ts +6 -0
  9. package/dist/core/preview/index.d.ts +2 -0
  10. package/dist/core/preview/mediaHelpers.d.ts +24 -0
  11. package/dist/core/preview/previewClockAggregator.d.ts +19 -0
  12. package/dist/core/preview/previewDomCompositor.d.ts +47 -0
  13. package/dist/core/preview/previewEngine.d.ts +86 -0
  14. package/dist/core/preview/previewPlaybackSlot.d.ts +59 -0
  15. package/dist/core/preview/previewPrefetchPool.d.ts +11 -0
  16. package/dist/core/preview/previewRuntimePublisher.d.ts +20 -0
  17. package/dist/core/preview/previewSourceService.d.ts +25 -0
  18. package/dist/core/preview/previewSyncCoordinator.d.ts +34 -0
  19. package/dist/core/preview/previewTextLayer.d.ts +46 -0
  20. package/dist/core/preview/textPreviewLayout.d.ts +55 -0
  21. package/dist/core/preview/types.d.ts +64 -0
  22. package/dist/index.cjs.js +113 -112
  23. package/dist/index.es.js +6418 -6872
  24. package/package.json +1 -1
  25. package/dist/core/controllers/previewRecoveryExecution.d.ts +0 -25
  26. package/dist/core/controllers/previewSlotLifecycle.d.ts +0 -42
  27. package/dist/core/controllers/previewSlotPolicy.d.ts +0 -59
  28. package/dist/core/controllers/timelinePreviewSession.d.ts +0 -218
package/README.md CHANGED
@@ -146,5 +146,5 @@ pnpm exec tsc -p tsconfig.json --noEmit
146
146
  - [docs/implementation-notes.md](./docs/implementation-notes.md): selection、preview、resource cache、draft 的实现笔记
147
147
  - [docs/resource-cache.md](./docs/resource-cache.md): 预览资源缓存的能力边界与配置
148
148
  - [docs/interaction-model.md](./docs/interaction-model.md): 指针交互分层和拖拽约束
149
- - [docs/preview-playback-recovery-flow.md](./docs/preview-playback-recovery-flow.md): 预览媒体时钟驱动播放、buffering 阻塞与恢复续播流程
149
+ - [docs/preview-clock-flow.md](./docs/preview-clock-flow.md): 预览媒体时钟驱动播放与 buffering 阻塞流程
150
150
  - [docs/testing-harness-roadmap.md](./docs/testing-harness-roadmap.md): Harness Engineering 导向的测试与诊断重构方案
@@ -1,6 +1,6 @@
1
1
  import type { TimelinePreviewBackend, TimelinePreviewBackendOptions } from './previewBackend';
2
- import { TimelinePreviewSession } from './timelinePreviewSession';
3
- export declare class DomPreviewBackend extends TimelinePreviewSession implements TimelinePreviewBackend {
2
+ import { PreviewEngine } from '../preview/previewEngine';
3
+ export declare class DomPreviewBackend extends PreviewEngine implements TimelinePreviewBackend {
4
4
  constructor(options?: TimelinePreviewBackendOptions);
5
5
  destroy(): void;
6
6
  }
@@ -7,7 +7,8 @@ export { PreviewClockController } from './previewClockController';
7
7
  export * from './timelineDurationController';
8
8
  export { TimelineEventDispatcher } from './timelineEventDispatcher';
9
9
  export { TimelinePlaybackResolver } from './timelinePlaybackResolver';
10
- export { TimelinePreviewSession } from './timelinePreviewSession';
10
+ export { PreviewEngine } from '../preview/previewEngine';
11
+ export { PreviewEngine as TimelinePreviewSession } from '../preview/previewEngine';
11
12
  export { TimelinePreviewRuntimeController } from './timelinePreviewRuntimeController';
12
13
  export { TimelinePreviewStateController, type TimelinePendingPreviewState } from './timelinePreviewStateController';
13
14
  export { TimelineKeyboardShortcutsController, type TimelineKeyboardShortcutsControllerCallbacks } from './timelineKeyboardShortcutsController';
@@ -23,6 +23,10 @@ export interface TimelinePreviewBackendCallbacks {
23
23
  onVisualTransformCommit?: (clipId: string, visualTransform: ClipVisualTransform) => void;
24
24
  onTextFontSizeCommit?: (clipId: string, fontSize: number) => void;
25
25
  onTextRotationCommit?: (clipId: string, rotation: number) => void;
26
+ onTextLayoutCommit?: (clipId: string, layout: {
27
+ layoutWidth: number;
28
+ layoutHeight: number;
29
+ }) => void;
26
30
  onPendingPreviewRetry?: () => void;
27
31
  onRuntimeError?: (error: unknown) => void;
28
32
  /** 预览层文字交互(点击字幕或文字变换控件)时若时间线处于播放中,用于请求暂停 */
@@ -16,10 +16,16 @@ export interface PreviewMediaSize {
16
16
  height: number;
17
17
  }
18
18
  export declare const DEFAULT_CLIP_VISUAL_TRANSFORM: ClipVisualTransform;
19
+ /** 文本预览底边与画幅底边的默认间距(相对画幅高度)。 */
20
+ export declare const TEXT_CLIP_BOTTOM_MARGIN_RATIO = 0.06;
21
+ /** 新建文本时的中心锚点默认位(基于 720 高 baseline 与估算框高)。 */
22
+ export declare const DEFAULT_TEXT_CLIP_VISUAL_TRANSFORM: ClipVisualTransform;
19
23
  export declare const MIN_CLIP_VISUAL_SCALE = 0.1;
20
24
  export declare const MAX_CLIP_VISUAL_SCALE = 8;
21
25
  export declare function clampClipVisualScale(scale: number): number;
22
26
  export declare function resolveClipVisualTransform(transform?: Partial<ClipVisualTransform> | null): ClipVisualTransform;
27
+ /** 文本位移使用框中心锚点;预览布局不使用 scale(固定为 1)。 */
28
+ export declare function resolveTextClipVisualTransform(transform?: Partial<ClipVisualTransform> | null): ClipVisualTransform;
23
29
  export declare function areClipVisualTransformsEqual(left?: Partial<ClipVisualTransform> | null, right?: Partial<ClipVisualTransform> | null, epsilon?: number): boolean;
24
30
  export declare function calculateContainRect(frame: PreviewFrameSize, media: PreviewMediaSize): PreviewRect;
25
31
  export declare function calculatePreviewDisplayRect(frame: PreviewFrameSize, media: PreviewMediaSize, transform?: Partial<ClipVisualTransform> | null): PreviewRect;
@@ -9,6 +9,11 @@ export interface PreviewTransformOverlayState {
9
9
  clipType?: 'text' | 'video';
10
10
  currentFontSize?: number;
11
11
  currentRotation?: number;
12
+ currentLayoutWidth?: number;
13
+ currentLayoutHeight?: number;
14
+ /** 文本选中时把手柄挂到此元素,与文案共用一个框 */
15
+ textHostElement?: HTMLElement | null;
16
+ baselineScale?: number;
12
17
  }
13
18
  interface PreviewTransformOverlayCallbacks {
14
19
  onPreviewTransformChange?: (clipId: string, transform: ClipVisualTransform | null) => void;
@@ -17,6 +22,14 @@ interface PreviewTransformOverlayCallbacks {
17
22
  onFontSizeCommit?: (clipId: string, fontSize: number) => void;
18
23
  onRotationChange?: (clipId: string, rotation: number | null) => void;
19
24
  onRotationCommit?: (clipId: string, rotation: number) => void;
25
+ onTextLayoutChange?: (clipId: string, layout: {
26
+ layoutWidth: number;
27
+ layoutHeight: number;
28
+ } | null) => void;
29
+ onTextLayoutCommit?: (clipId: string, layout: {
30
+ layoutWidth: number;
31
+ layoutHeight: number;
32
+ }) => void;
20
33
  /** 文字片段预览变换框上开始拖拽/缩放/旋转(播放中时应暂停) */
21
34
  onTextTransformInteractionStart?: () => void;
22
35
  }
@@ -30,6 +43,7 @@ export declare class PreviewTransformOverlay {
30
43
  private readonly handlePointerDownHandlers;
31
44
  private state;
32
45
  private interaction;
46
+ private textHostElement;
33
47
  private readonly callbacks;
34
48
  private readonly boundPointerMove;
35
49
  private readonly boundPointerUp;
@@ -39,6 +53,8 @@ export declare class PreviewTransformOverlay {
39
53
  attach(frameElement: HTMLElement): void;
40
54
  detach(): void;
41
55
  sync(state: PreviewTransformOverlayState | null): void;
56
+ /** 在已选中的文本框上按下时启动移动(与手柄同属一个框) */
57
+ handleTextHostPointerDown(event: Event): void;
42
58
  private createRotateHandle;
43
59
  private getRotateHandlePointerDown;
44
60
  private createHandle;
@@ -49,6 +65,11 @@ export declare class PreviewTransformOverlay {
49
65
  private handleDocumentPointerMove;
50
66
  private finishInteraction;
51
67
  private render;
68
+ private renderTextHostHandles;
69
+ private renderOverlayBox;
70
+ private renderRotateHandle;
71
+ private setTextHost;
72
+ private mountHandlesOn;
52
73
  private positionHandle;
53
74
  private toLocalPoint;
54
75
  private getFrameBounds;
@@ -640,6 +640,7 @@ export declare class TimelineManager {
640
640
  private resolveSelectedClipSnapshot;
641
641
  private resolveSelectionChangeData;
642
642
  private commitPreviewVisualTransform;
643
+ private commitPreviewTextLayout;
643
644
  private commitPreviewTextFontSize;
644
645
  private commitPreviewTextRotation;
645
646
  clearAllTracksAndClips(): void;
@@ -23,6 +23,12 @@ export interface TextClipStyle {
23
23
  strokeColor: string;
24
24
  strokeWidth: number;
25
25
  paddingX: number;
26
+ /** 文本区域宽度(baseline 画布像素)。 */
27
+ layoutWidth?: number;
28
+ /** 文本区域高度(baseline 画布像素);未设时由内容估算。 */
29
+ layoutHeight?: number;
30
+ /** 1 = visualTransform 为框中心锚点。 */
31
+ anchorVersion?: number;
26
32
  }
27
33
  export declare const DEFAULT_TEXT_CLIP_STYLE: TextClipStyle;
28
34
  export declare function resolveTextClipStyle(style?: Partial<TextClipStyle> | null): TextClipStyle;
@@ -0,0 +1,2 @@
1
+ export { PreviewEngine, type PreviewEngineDependencies } from './previewEngine';
2
+ export { PreviewSourceService } from './previewSourceService';
@@ -0,0 +1,24 @@
1
+ import type { ActiveClipPlaybackInfo } from '../models/types';
2
+ import type { DiagnosticMediaState } from '../../utils/diagnostics';
3
+ export declare const HAVE_CURRENT_DATA_STATE = 2;
4
+ export declare const HAVE_FUTURE_DATA_STATE = 3;
5
+ export declare const STEADY_PLAYBACK_CURRENT_SLOT_SEEK_THRESHOLD_SECONDS = 0.5;
6
+ export declare const CLOCK_SOURCE_TARGET_ALIGNMENT_THRESHOLD_MS = 750;
7
+ export declare const SOURCE_RESOLVE_TIMEOUT_MS = 15000;
8
+ export declare const PREVIEW_SLOT_FAILURE_MESSAGE = "\u5A92\u4F53\u4E22\u5931";
9
+ export declare function isPromiseLike<T>(value: T | Promise<T>): value is Promise<T>;
10
+ export declare function getAssignedSrc(element: HTMLMediaElement): string;
11
+ export declare function getConfiguredSrc(element: HTMLMediaElement): string;
12
+ export declare function getMediaSourceDebugState(element: HTMLMediaElement, desiredSource?: string | null): Record<string, unknown>;
13
+ export declare function isVideoMediaElement(element: HTMLMediaElement): element is HTMLVideoElement;
14
+ export declare function safeLoad(element: HTMLMediaElement): void;
15
+ export declare function safePause(element: HTMLMediaElement): void;
16
+ export declare function safePlay(element: HTMLMediaElement): void;
17
+ export declare function safeSetCurrentTime(element: HTMLMediaElement, nextTimeSeconds: number): void;
18
+ export declare function getClipStyleDimension(entry: ActiveClipPlaybackInfo | null, widthKey: 'sys_width' | 'sys_height'): number | null;
19
+ export declare function parsePxValue(value?: string): number;
20
+ export declare function getBufferedEnd(element: HTMLMediaElement): number | null;
21
+ export declare function getMediaDebugState(element: HTMLMediaElement): DiagnosticMediaState;
22
+ export declare function doesSlotSourceMatchDesired(configuredSource: string | null, assignedSource: string | null, desiredSource: string | null): boolean;
23
+ export declare function getSlotSeekThresholdSeconds(role: 'current' | 'preload', playbackRate: number): number;
24
+ export declare function buildPreviewSourceCacheKey(clip: ActiveClipPlaybackInfo['clip']): string;
@@ -0,0 +1,19 @@
1
+ import type { TimeMs } from '../models/types';
2
+ import type { PreviewClockState, PreviewLoadingState } from '../controllers/previewBackend';
3
+ import type { PlaybackSlotState } from './types';
4
+ export declare class PreviewClockAggregator {
5
+ private pendingClockAlignmentTargetTime;
6
+ updateAlignmentTarget(hasActiveVisualVideo: boolean, interactionMode: 'steady' | 'seek' | 'scrub' | undefined, currentTime: TimeMs): void;
7
+ buildClockState(loading: PreviewLoadingState, slots: PlaybackSlotState[], playbackGroupSuspended: boolean): PreviewClockState;
8
+ private getClockSourceSlot;
9
+ private buildCandidate;
10
+ private compareCandidates;
11
+ private isCandidateAligned;
12
+ private getAlignmentThresholdMs;
13
+ private isMediaTimeAligned;
14
+ private isTimelineAligned;
15
+ private getMediaTimeMs;
16
+ private getTimelineTime;
17
+ private buildFromSlot;
18
+ private resolveBlockedReason;
19
+ }
@@ -0,0 +1,47 @@
1
+ import type { ActiveClipPlaybackInfo, PreviewAspectRatio } from '../models/types';
2
+ import { resolveClipVisualTransform, type PreviewFrameSize } from '../controllers/previewTransformMath';
3
+ import { PreviewTransformOverlay, type PreviewTransformOverlayState } from '../controllers/previewTransformOverlay';
4
+ import { type TextLayoutStyleOverride } from './textPreviewLayout';
5
+ import type { PreviewPlaybackSlot } from './previewPlaybackSlot';
6
+ export declare class PreviewDomCompositor {
7
+ private container;
8
+ private rootElement;
9
+ private frameElement;
10
+ private resizeObserver;
11
+ private requestedAspectRatio;
12
+ private resolvedAutoAspectRatio;
13
+ readonly transformOverlay: PreviewTransformOverlay;
14
+ constructor(transformOverlay: PreviewTransformOverlay);
15
+ attach(container: HTMLElement, options: {
16
+ rootClassName?: string;
17
+ frameClassName?: string;
18
+ }): void;
19
+ detach(): void;
20
+ getFrameElement(): HTMLDivElement | null;
21
+ getRootElement(): HTMLDivElement | null;
22
+ applyAspectRatio(aspectRatio: PreviewAspectRatio): void;
23
+ getAspectRatio(): PreviewAspectRatio;
24
+ resolveAutoAspectRatio(width: number, height: number, onChange?: (ratio: PreviewAspectRatio) => void): void;
25
+ tryResolveAutoAspectRatioFromSlot(slot: PreviewPlaybackSlot, onChange?: (ratio: PreviewAspectRatio) => void): void;
26
+ refreshSlotVisual(slot: PreviewPlaybackSlot): void;
27
+ buildSelectedOverlayState(primarySelectedClipId: string | null, slots: PreviewPlaybackSlot[], textOverlay: {
28
+ getEntry: (clipId: string) => {
29
+ entry: ActiveClipPlaybackInfo;
30
+ element: HTMLElement;
31
+ } | undefined;
32
+ getTransientFontSize: (clipId: string) => number | null;
33
+ getTransientRotation: (clipId: string) => number | null;
34
+ getTextLayoutOverride?: (clipId: string) => TextLayoutStyleOverride | null;
35
+ }, getEffectiveVisualTransform: (entry: ActiveClipPlaybackInfo) => ReturnType<typeof resolveClipVisualTransform>): PreviewTransformOverlayState | null;
36
+ getFrameSize(): PreviewFrameSize;
37
+ getTextBaselineMetrics(frameSize: PreviewFrameSize): {
38
+ width: number;
39
+ height: number;
40
+ scale: number;
41
+ };
42
+ moveTransform(startTransform: ReturnType<typeof resolveClipVisualTransform>, deltaX: number, deltaY: number): ReturnType<typeof resolveClipVisualTransform>;
43
+ transformsEqual(left: ReturnType<typeof resolveClipVisualTransform>, right: ReturnType<typeof resolveClipVisualTransform>): boolean;
44
+ private updateFrameLayout;
45
+ private getMediaSize;
46
+ private syncFailureVisual;
47
+ }
@@ -0,0 +1,86 @@
1
+ import type { ActiveClipPlaybackInfo, PreviewAspectRatio, PreviewSourceResolver, TextPreviewFontConfig, TrackType } from '../models/types';
2
+ import type { PreviewPendingState, PreviewRuntimeState, TimelinePreviewSyncPayload } from '../controllers/previewBackend';
3
+ import type { SourceMediaRegistry } from '../resources/sourceMediaRegistry';
4
+ import type { ResourceCacheManager } from '../resources/resourceCache';
5
+ import { type DiagnosticEmitInput, type DiagnosticsCenter } from '../../utils/diagnostics';
6
+ import { PreviewPlaybackSlot } from './previewPlaybackSlot';
7
+ import type { PreviewEngineCallbacks } from './types';
8
+ export interface PreviewEngineDependencies {
9
+ createMediaElement?: (kind: TrackType, role: 'current' | 'preload') => HTMLMediaElement;
10
+ createAspectRatioProbe?: () => HTMLVideoElement;
11
+ createAudioContext?: () => AudioContext | null;
12
+ diagnostics?: DiagnosticsCenter;
13
+ getDiagnosticsContext?: () => Partial<DiagnosticEmitInput>;
14
+ rootClassName?: string;
15
+ frameClassName?: string;
16
+ slotClassNamePrefix?: string;
17
+ previewSourceResolver?: PreviewSourceResolver;
18
+ textPreviewFont?: TextPreviewFontConfig | null;
19
+ sourceMediaRegistry?: SourceMediaRegistry;
20
+ resourceCacheManager?: ResourceCacheManager | null;
21
+ }
22
+ export declare class PreviewEngine {
23
+ private container;
24
+ private pendingOverlayElement;
25
+ private readonly pendingOverlayRenderer;
26
+ private readonly compositor;
27
+ private readonly textLayer;
28
+ private readonly sourceService;
29
+ private readonly syncCoordinator;
30
+ private readonly clockAggregator;
31
+ private readonly runtimePublisher;
32
+ private readonly trackSlots;
33
+ private readonly callbacks;
34
+ private readonly dependencies;
35
+ private audioContext;
36
+ private masterGainNode;
37
+ private loadingProbeTimeoutId;
38
+ private clockProbeTimeoutId;
39
+ private playbackGroupSuspended;
40
+ private primarySelectedClipId;
41
+ private transientVisualTransform;
42
+ private transientFontSize;
43
+ private transientTextLayout;
44
+ private transientRotation;
45
+ private pendingState;
46
+ private activeSyncRequestId;
47
+ private isSyncProjecting;
48
+ private lastSyncedPlayState;
49
+ private aspectRatioProbe;
50
+ private aspectRatioProbeSrc;
51
+ constructor(callbacks?: PreviewEngineCallbacks, dependencies?: PreviewEngineDependencies);
52
+ hasPreview(): boolean;
53
+ attach(container: HTMLElement): void;
54
+ detach(): void;
55
+ sync(payload: TimelinePreviewSyncPayload): void;
56
+ applyAspectRatio(aspectRatio: PreviewAspectRatio): void;
57
+ getAspectRatio(): PreviewAspectRatio;
58
+ setPendingState(state: PreviewPendingState | null): void;
59
+ failSlot(slot: PreviewPlaybackSlot, _entry: ActiveClipPlaybackInfo, message: string): void;
60
+ get lastRuntimeState(): PreviewRuntimeState;
61
+ refreshRuntimeState(): void;
62
+ private applyTrackPlan;
63
+ private getTrackSlots;
64
+ private collectSlots;
65
+ private buildLoadingState;
66
+ private buildTrackedSummary;
67
+ private syncPlaybackGroupSuspension;
68
+ private configureAudioRouting;
69
+ private getAudioContext;
70
+ private getMasterGainNode;
71
+ private bootstrapLayoutAspectRatioFromClipStyle;
72
+ private maybeResolveAutoAspectRatio;
73
+ private getAspectRatioProbe;
74
+ private getEffectiveVisualTransform;
75
+ private getTextLayoutOverride;
76
+ private getTextLayoutOptions;
77
+ private handlePreviewTransformChange;
78
+ refreshVisualLayout(): void;
79
+ private refreshPendingOverlay;
80
+ private syncLoadingProbe;
81
+ private syncClockProbe;
82
+ private clearProbes;
83
+ private clearLoadingProbe;
84
+ private clearClockProbe;
85
+ private emitDiagnostic;
86
+ }
@@ -0,0 +1,59 @@
1
+ import type { ActiveClipPlaybackInfo, PlayState, TrackType } from '../models/types';
2
+ import type { PreviewSlotPhase } from '../controllers/previewBackend';
3
+ import type { PlaybackSlotState, ResolvedSlotSource, TrackedVideoSlotStatus } from './types';
4
+ export interface PreviewPlaybackSlotOptions {
5
+ createMediaElement?: (kind: TrackType, role: 'current' | 'preload') => HTMLMediaElement;
6
+ slotClassNamePrefix?: string;
7
+ onStateChange?: () => void;
8
+ onMediaError?: (slot: PreviewPlaybackSlot, message: string) => void;
9
+ onDiagnostic?: (eventName: string, slot: PreviewPlaybackSlot, extra?: Record<string, unknown>) => void;
10
+ }
11
+ export declare class PreviewPlaybackSlot implements PlaybackSlotState {
12
+ trackId: string;
13
+ kind: TrackType;
14
+ role: 'current' | 'preload';
15
+ element: HTMLMediaElement;
16
+ wrapper: HTMLDivElement | null;
17
+ failureOverlay: HTMLDivElement | null;
18
+ failureLabel: HTMLDivElement | null;
19
+ entry: ActiveClipPlaybackInfo | null;
20
+ stableSourceUrl: string | null;
21
+ boundStableKey: string | null;
22
+ desiredSource: string | null;
23
+ objectUrl: string | null;
24
+ requestedPlayState: PlayState;
25
+ isActive: boolean;
26
+ isLoading: boolean;
27
+ isResolvingSource: boolean;
28
+ isBuffering: boolean;
29
+ isSeeking: boolean;
30
+ phase: PreviewSlotPhase;
31
+ errorMessage: string | null;
32
+ hasReadyEventSinceSourceChange: boolean;
33
+ pendingClockAlignmentMediaTime: number | null;
34
+ sourceGen: number;
35
+ sourceNode: MediaElementAudioSourceNode | null;
36
+ gainNode: GainNode | null;
37
+ audioRoutingFailed: boolean;
38
+ cleanup: Array<() => void>;
39
+ private lastPlaybackResumeAttemptAtMs;
40
+ private readonly options;
41
+ constructor(trackId: string, kind: TrackType, role: 'current' | 'preload', options?: PreviewPlaybackSlotOptions);
42
+ mountVideoTo(frame: HTMLElement): void;
43
+ mountAudioTo(root: HTMLElement): void;
44
+ clear(): void;
45
+ fail(message: string): void;
46
+ clearFailureIfClipChanged(): void;
47
+ assignSource(resolved: ResolvedSlotSource, stableKey: string): boolean;
48
+ applyPlayback(entry: ActiveClipPlaybackInfo, playState: PlayState, speed: number, visible: boolean, zIndex: number): void;
49
+ sourceMatchesDesired(): boolean;
50
+ maybeResumePlayback(reason: 'canplay' | 'seeked' | 'runtime-check'): void;
51
+ destroy(releaseObjectUrl: (url: string | null) => void): void;
52
+ private syncCurrentPlayback;
53
+ private preparePreload;
54
+ private setVisible;
55
+ private createMediaElement;
56
+ private setupWrapper;
57
+ private bindMediaEvents;
58
+ }
59
+ export declare function evaluateTrackedVideoSlot(slot: PlaybackSlotState, isTracked: boolean): TrackedVideoSlotStatus;
@@ -0,0 +1,11 @@
1
+ import type { ActiveClipPlaybackInfo } from '../models/types';
2
+ import type { PreviewSlotTarget } from '../controllers/previewTrackPlanner';
3
+ import type { PreviewPlaybackSlot } from './previewPlaybackSlot';
4
+ /**
5
+ * Prefetch pool schedules warm loading on preload slots without affecting
6
+ * playback failure state or preview clock aggregation.
7
+ */
8
+ export declare class PreviewPrefetchPool {
9
+ shouldSchedule(target: PreviewSlotTarget): boolean;
10
+ onPrefetchFailure(slot: PreviewPlaybackSlot, _entry: ActiveClipPlaybackInfo, reason: string): void;
11
+ }
@@ -0,0 +1,20 @@
1
+ import type { PreviewRuntimeState } from '../controllers/previewBackend';
2
+ import type { PreviewPlaybackSlot } from './previewPlaybackSlot';
3
+ import type { TrackedVideoSlotStatus } from './types';
4
+ export interface RuntimePublisherInput {
5
+ activeSyncRequestId?: number;
6
+ isSyncProjecting: boolean;
7
+ slots: PreviewPlaybackSlot[];
8
+ evaluateTracked: (slot: PreviewPlaybackSlot) => TrackedVideoSlotStatus;
9
+ isTrackedVideo: (slot: PreviewPlaybackSlot) => boolean;
10
+ clock: PreviewRuntimeState['clock'];
11
+ }
12
+ export declare class PreviewRuntimePublisher {
13
+ private lastSignature;
14
+ private lastState;
15
+ private lastSettledSyncRequestId;
16
+ getLastState(): PreviewRuntimeState;
17
+ publish(input: RuntimePublisherInput, onChange: (state: PreviewRuntimeState) => void, onSyncProcessed?: (syncRequestId?: number) => void): PreviewRuntimeState;
18
+ reset(): void;
19
+ private buildLoadingState;
20
+ }
@@ -0,0 +1,25 @@
1
+ import type { ActiveClipPlaybackInfo, PreviewSourceResolver } from '../models/types';
2
+ import type { SourceMediaRegistry } from '../resources/sourceMediaRegistry';
3
+ import type { ResourceCacheManager } from '../resources/resourceCache';
4
+ import type { ResolvedSlotSource } from './types';
5
+ export interface PreviewSourceServiceDependencies {
6
+ previewSourceResolver?: PreviewSourceResolver;
7
+ sourceMediaRegistry?: SourceMediaRegistry;
8
+ resourceCacheManager?: ResourceCacheManager | null;
9
+ onDiagnostic?: (eventName: string, data: Record<string, unknown>) => void;
10
+ }
11
+ export declare class PreviewSourceService {
12
+ private readonly dependencies;
13
+ private readonly pendingRuntime;
14
+ constructor(dependencies?: PreviewSourceServiceDependencies);
15
+ resolve(entry: ActiveClipPlaybackInfo, options: {
16
+ sourceGen: number;
17
+ getCurrentSourceGen: () => number;
18
+ reuse?: ResolvedSlotSource | null;
19
+ }): Promise<ResolvedSlotSource | null> | ResolvedSlotSource | null;
20
+ getReusableSource(boundStableKey: string | null, stableSourceUrl: string | null, desiredSource: string | null, objectUrl: string | null, entry: ActiveClipPlaybackInfo): ResolvedSlotSource | null;
21
+ revokeObjectUrl(objectUrl: string | null | undefined): void;
22
+ private resolvePlayable;
23
+ private normalizeCachedResult;
24
+ private emit;
25
+ }
@@ -0,0 +1,34 @@
1
+ import type { ActiveClipPlaybackInfo, TrackType } from '../models/types';
2
+ import type { PreviewSlotTarget, PreviewTrackPlan } from '../controllers/previewTrackPlanner';
3
+ import { evaluateTrackedVideoSlot, PreviewPlaybackSlot } from './previewPlaybackSlot';
4
+ import type { PreviewSourceService } from './previewSourceService';
5
+ export interface TrackSlotPair {
6
+ current: PreviewPlaybackSlot;
7
+ preload: PreviewPlaybackSlot;
8
+ }
9
+ export interface PreviewSyncCoordinatorOptions {
10
+ sourceService: PreviewSourceService;
11
+ getTrackSlots: (trackId: string, kind: TrackType) => TrackSlotPair;
12
+ onAsyncWorkStarted: () => void;
13
+ onAsyncWorkFinished: () => void;
14
+ onStateChange: () => void;
15
+ releaseObjectUrl: (url: string | null) => void;
16
+ rememberObjectUrl: (slot: PreviewPlaybackSlot, url: string | null) => void;
17
+ configureAudio: (slot: PreviewPlaybackSlot, entry: ActiveClipPlaybackInfo) => void;
18
+ syncClockAlignment: (slot: PreviewPlaybackSlot, target: PreviewSlotTarget, clipChanged: boolean) => void;
19
+ isCurrentTarget: (slot: PreviewPlaybackSlot, entry: ActiveClipPlaybackInfo, stableKey: string) => boolean;
20
+ }
21
+ export declare class PreviewSyncCoordinator {
22
+ private syncGen;
23
+ private pendingAsyncCount;
24
+ private readonly options;
25
+ constructor(options: PreviewSyncCoordinatorOptions);
26
+ get isProjecting(): boolean;
27
+ applyTrackPlan(plan: PreviewTrackPlan): void | Promise<void>;
28
+ bumpSyncGeneration(): number;
29
+ private applySlotTarget;
30
+ private beginAsyncWork;
31
+ private endAsyncWork;
32
+ }
33
+ export declare function isTrackedActiveCurrentVideoSlot(slot: PreviewPlaybackSlot): boolean;
34
+ export declare function evaluateSlotForRuntime(slot: PreviewPlaybackSlot): ReturnType<typeof evaluateTrackedVideoSlot>;
@@ -0,0 +1,46 @@
1
+ import type { ActiveClipPlaybackInfo, ClipVisualTransform, TextPreviewFontConfig } from '../models/types';
2
+ import type { PreviewDomCompositor } from './previewDomCompositor';
3
+ import { type TextLayoutStyleOverride, type TextPreviewBoxLayout } from './textPreviewLayout';
4
+ export type { TextLayoutStyleOverride } from './textPreviewLayout';
5
+ export { resolveTextEntryMaxWidthPx } from './textPreviewLayout';
6
+ export interface RefreshTextLayoutOptions {
7
+ getStyleOverride?: (entry: ActiveClipPlaybackInfo) => TextLayoutStyleOverride | null;
8
+ resolveVisualTransform?: (entry: ActiveClipPlaybackInfo) => ClipVisualTransform;
9
+ }
10
+ /** 统一文本框 DOM 层:布局仅来自 computeTextPreviewBox。 */
11
+ export declare class PreviewTextLayer {
12
+ private overlayRoot;
13
+ private readonly entries;
14
+ private fontStyleElement;
15
+ private fontSignature;
16
+ private fontLoadState;
17
+ private fontLoadToken;
18
+ private primarySelectedClipId;
19
+ private textPreviewFont;
20
+ private readonly compositor;
21
+ private readonly callbacks;
22
+ constructor(compositor: PreviewDomCompositor, callbacks: PreviewTextLayer['callbacks']);
23
+ mount(frameElement: HTMLDivElement): HTMLDivElement;
24
+ destroy(): void;
25
+ setPrimarySelectedClipId(clipId: string | null): void;
26
+ setTextPreviewFont(config: TextPreviewFontConfig | null): void;
27
+ syncEntries(activeEntries: ActiveClipPlaybackInfo[], resolveVisualTransform: (entry: ActiveClipPlaybackInfo) => ClipVisualTransform, layoutOptions?: RefreshTextLayoutOptions): void;
28
+ refreshLayout(resolveVisualTransform: (entry: ActiveClipPlaybackInfo) => ClipVisualTransform, options?: RefreshTextLayoutOptions): void;
29
+ getEntry(clipId: string): {
30
+ entry: ActiveClipPlaybackInfo;
31
+ element: HTMLDivElement;
32
+ layout: TextPreviewBoxLayout | null;
33
+ } | undefined;
34
+ getLayout(clipId: string): TextPreviewBoxLayout | null;
35
+ private resolveBaselineScale;
36
+ private reapplyAllContent;
37
+ private applyBoxLayout;
38
+ private applyContent;
39
+ private getFontFamily;
40
+ private ensureFontReady;
41
+ private resetFontState;
42
+ private clearEntries;
43
+ private removeEntry;
44
+ private createPointerDownHandler;
45
+ private refreshInteractionState;
46
+ }
@@ -0,0 +1,55 @@
1
+ import type { ClipVisualTransform, TextClipStyle } from '../models/types';
2
+ import { type PreviewFrameSize, type PreviewRect } from '../controllers/previewTransformMath';
3
+ /** 文本 visualTransform 使用框中心锚点。 */
4
+ export declare const TEXT_CLIP_ANCHOR_VERSION_CENTER = 1;
5
+ export interface TextLayoutStyleOverride {
6
+ fontSize?: number;
7
+ rotation?: number;
8
+ layoutWidth?: number;
9
+ layoutHeight?: number;
10
+ }
11
+ export interface TextPreviewBoxLayout {
12
+ layoutWidth: number;
13
+ layoutHeight: number;
14
+ frameRect: PreviewRect;
15
+ visualTransform: ClipVisualTransform;
16
+ }
17
+ export interface ComputeTextPreviewBoxInput {
18
+ frameSize: PreviewFrameSize;
19
+ baselineScale: number;
20
+ visualTransform: ClipVisualTransform;
21
+ textStyle: TextClipStyle;
22
+ contentText: string;
23
+ styleOverride?: TextLayoutStyleOverride | null;
24
+ }
25
+ /** 将 baseline 画布上的字号/内边距/描边换算为当前预览帧像素(短边 720 等比缩放)。 */
26
+ export declare function scaleTextMetricsToFrame(fontSize: number, paddingX: number, strokeWidth: number, baselineScale: number): {
27
+ fontSize: number;
28
+ paddingX: number;
29
+ strokeWidth: number;
30
+ };
31
+ /** 布局换行宽度 fallback(仅 migrate / 无 layoutWidth 时)。 */
32
+ export declare function resolveTextEntryMaxWidthPx(frameWidth: number, baselineScale: number, paddingX: number, anchorX?: number): number;
33
+ export declare function estimateTextLayoutHeightBaseline(fontSize: number, contentText: string, layoutWidthBaseline: number, paddingX: number): number;
34
+ export declare function resolveDefaultTextCenterTransform(frameSize: PreviewFrameSize, layoutHeightBaseline: number, baselineScale: number): ClipVisualTransform;
35
+ export declare function resolveDefaultTextCenterTransformForBaselineFrame(baselineFrameHeight: number, layoutHeightBaseline?: number): ClipVisualTransform;
36
+ export declare function migrateBottomAnchorTransformToCenter(transform: ClipVisualTransform, frameSize: PreviewFrameSize, displayHeightPx: number): ClipVisualTransform;
37
+ export declare function resolveTextVisualTransformForPreview(transform: Partial<ClipVisualTransform> | null | undefined, textStyle: TextClipStyle, frameSize: PreviewFrameSize, displayHeightPx: number): ClipVisualTransform;
38
+ export declare function resolveTextLayoutDimensions(input: ComputeTextPreviewBoxInput): {
39
+ layoutWidth: number;
40
+ layoutHeight: number;
41
+ };
42
+ export declare function computeTextPreviewBox(input: ComputeTextPreviewBoxInput): TextPreviewBoxLayout;
43
+ /** 按指针相对框中心的轴向距离独立缩放宽高(不保持宽高比)。 */
44
+ export declare function scaleTextLayoutFromPointer(pointerX: number, pointerY: number, centerX: number, centerY: number, baselineScale: number): {
45
+ layoutWidth: number;
46
+ layoutHeight: number;
47
+ };
48
+ export declare function buildTextPreviewBoxForEntry(entry: {
49
+ clip: {
50
+ textStyle?: Partial<TextClipStyle> | null;
51
+ textContent?: string;
52
+ name?: string;
53
+ visualTransform?: Partial<ClipVisualTransform> | null;
54
+ };
55
+ }, frameSize: PreviewFrameSize, baselineScale: number, styleOverride?: TextLayoutStyleOverride | null): TextPreviewBoxLayout;
@@ -0,0 +1,64 @@
1
+ import type { ActiveClipPlaybackInfo, PlayState, TrackType } from '../models/types';
2
+ import type { PreviewSlotPhase } from '../controllers/previewBackend';
3
+ export interface ResolvedSlotSource {
4
+ stableSourceUrl: string;
5
+ url: string;
6
+ objectUrl: string | null;
7
+ sizeBytes: number | null;
8
+ }
9
+ export interface SlotGeneration {
10
+ syncGen: number;
11
+ sourceGen: number;
12
+ }
13
+ export interface PlaybackSlotState {
14
+ trackId: string;
15
+ kind: TrackType;
16
+ role: 'current' | 'preload';
17
+ element: HTMLMediaElement;
18
+ wrapper: HTMLDivElement | null;
19
+ failureOverlay: HTMLDivElement | null;
20
+ failureLabel: HTMLDivElement | null;
21
+ entry: ActiveClipPlaybackInfo | null;
22
+ stableSourceUrl: string | null;
23
+ boundStableKey: string | null;
24
+ desiredSource: string | null;
25
+ objectUrl: string | null;
26
+ requestedPlayState: PlayState;
27
+ isActive: boolean;
28
+ isLoading: boolean;
29
+ isResolvingSource: boolean;
30
+ isBuffering: boolean;
31
+ isSeeking: boolean;
32
+ phase: PreviewSlotPhase;
33
+ errorMessage: string | null;
34
+ hasReadyEventSinceSourceChange: boolean;
35
+ pendingClockAlignmentMediaTime: number | null;
36
+ sourceGen: number;
37
+ sourceNode: MediaElementAudioSourceNode | null;
38
+ gainNode: GainNode | null;
39
+ audioRoutingFailed: boolean;
40
+ cleanup: Array<() => void>;
41
+ sourceMatchesDesired(): boolean;
42
+ }
43
+ export interface TrackedVideoSlotStatus {
44
+ failed: boolean;
45
+ resolving: boolean;
46
+ seeking: boolean;
47
+ mediaUnready: boolean;
48
+ ready: boolean;
49
+ }
50
+ export interface PreviewEngineCallbacks {
51
+ onSyncProcessed?: (syncRequestId?: number) => void;
52
+ onRuntimeStateChange?: (state: import('../controllers/previewBackend').PreviewRuntimeState) => void;
53
+ onAspectRatioChange?: (aspectRatio: import('../models/types').PreviewAspectRatio) => void;
54
+ onPreviewClipSelect?: (clipId: string) => void;
55
+ onVisualTransformCommit?: (clipId: string, visualTransform: import('../models/types').ClipVisualTransform) => void;
56
+ onTextFontSizeCommit?: (clipId: string, fontSize: number) => void;
57
+ onTextRotationCommit?: (clipId: string, rotation: number) => void;
58
+ onTextLayoutCommit?: (clipId: string, layout: {
59
+ layoutWidth: number;
60
+ layoutHeight: number;
61
+ }) => void;
62
+ onPendingPreviewRetry?: () => void;
63
+ onPauseIfPlaying?: () => void;
64
+ }