@linker-design-plus/timeline-track 2.0.23 → 2.1.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.
package/README.md CHANGED
@@ -176,6 +176,9 @@ interface ClipConfig {
176
176
  - 当前代码路径实际只会创建 `DomPreviewBackend`
177
177
  - `previewBackend` 字段被保留,目的是不给后续预览实现扩展制造额外 breaking change
178
178
  - 如果你在业务接入层看到 `canvas` / `auto` 配置,请把它当作保留参数,而不是当前版本的有效能力
179
+ - 挂载预览且当前时间命中视频片段时,播放时间由预览视频的真实媒体时钟驱动;媒体 buffering 或 stalled 导致画面没有前进时,播放指针也不会继续超前
180
+ - 多视频叠加播放时,任意一路 active 视频仍在加载或卡顿,预览层会暂停整个 active 播放组,等当前时刻所有视频都 ready 后统一恢复
181
+ - 空白段、纯音频、文本或未挂载预览时,播放仍回退到 `TimelineManager` 的 wall-clock 推进
179
182
 
180
183
  ## 架构概览
181
184
 
@@ -229,6 +232,7 @@ pnpm exec tsc -p tsconfig.json --noEmit
229
232
  ## 相关文档
230
233
 
231
234
  - [docs/interaction-model.md](./docs/interaction-model.md): 指针交互分层和拖拽约束
235
+ - [docs/preview-playback-recovery-flow.md](./docs/preview-playback-recovery-flow.md): 预览媒体时钟驱动播放、buffering 阻塞与恢复续播流程
232
236
  - [docs/refactor-roadmap.md](./docs/refactor-roadmap.md): 主线重构归档与后续收敛方向
233
237
  - [docs/maintenance-audit.md](./docs/maintenance-audit.md): 本轮仓库审计、验证结果和依赖建议
234
238
  - [docs/review-remediation-plan.md](./docs/review-remediation-plan.md): 基于审查结论的修复计划与阶段性落地建议
@@ -114,4 +114,6 @@ export declare class ClipConfigPanelRenderer extends LitDomRenderer<ClipConfigPa
114
114
  private getTransform;
115
115
  private calculatePresetPosition;
116
116
  private resizeSvg;
117
+ private static colorRafId;
118
+ private static createColorInputHandler;
117
119
  }
@@ -15,8 +15,6 @@ export interface TimelinePreviewSyncPayload {
15
15
  syncRequestId?: number;
16
16
  }
17
17
  export interface TimelinePreviewBackendCallbacks {
18
- onBufferingStateChange?: (isBuffering: boolean) => void;
19
- onSourceLoadingChange?: (pending: number) => void;
20
18
  onSyncProcessed?: (syncRequestId?: number) => void;
21
19
  onRuntimeStateChange?: (state: PreviewRuntimeState) => void;
22
20
  onAspectRatioChange?: (aspectRatio: PreviewAspectRatio) => void;
@@ -26,16 +24,34 @@ export interface TimelinePreviewBackendCallbacks {
26
24
  onTextRotationCommit?: (clipId: string, rotation: number) => void;
27
25
  onPendingPreviewRetry?: () => void;
28
26
  onRuntimeError?: (error: unknown) => void;
27
+ /** 预览层文字交互(点击字幕或文字变换控件)时若时间线处于播放中,用于请求暂停 */
28
+ onPauseIfPlaying?: () => void;
29
+ }
30
+ export type PreviewLoadingStatus = 'idle' | 'loading' | 'ready' | 'failed';
31
+ export type PreviewLoadingReason = 'syncing' | 'resolving-source' | 'seeking' | 'media-pending' | 'failed' | null;
32
+ export interface PreviewLoadingState {
33
+ status: PreviewLoadingStatus;
34
+ isLoading: boolean;
35
+ reason: PreviewLoadingReason;
29
36
  }
30
37
  export interface PreviewPendingState {
31
- mode: 'seek' | 'scrub';
38
+ mode: 'seek' | 'scrub' | 'playback';
32
39
  targetTime: TimeMs;
33
- loadingPending: number;
34
- isBuffering: boolean;
40
+ loading: PreviewLoadingState;
35
41
  errorMessage: string | null;
36
42
  }
37
43
  export type PreviewRuntimePhase = 'idle' | 'awaiting-sync' | 'awaiting-media' | 'ready' | 'failed';
38
44
  export type PreviewSlotPhase = 'idle' | 'binding' | 'primed' | 'active' | 'recovering' | 'failed';
45
+ export type PreviewClockStatus = 'unavailable' | 'priming' | 'running' | 'blocked' | 'failed';
46
+ export type PreviewClockBlockedReason = 'no-video-clock' | 'syncing' | 'resolving-source' | 'seeking' | 'media-pending' | 'media-paused' | 'media-stalled' | 'failed' | null;
47
+ export interface PreviewClockState {
48
+ status: PreviewClockStatus;
49
+ timelineTime: TimeMs | null;
50
+ mediaTime: TimeMs | null;
51
+ clipId: string | null;
52
+ trackId: string | null;
53
+ reason: PreviewClockBlockedReason;
54
+ }
39
55
  export interface PreviewSlotDiagnostic {
40
56
  trackId: string;
41
57
  role: 'current' | 'preload';
@@ -50,8 +66,8 @@ export interface PreviewSlotDiagnostic {
50
66
  export interface PreviewRuntimeState {
51
67
  syncRequestId?: number;
52
68
  phase: PreviewRuntimePhase;
53
- loadingCount: number;
54
- isBuffering: boolean;
69
+ loading: PreviewLoadingState;
70
+ clock?: PreviewClockState;
55
71
  errorMessage: string | null;
56
72
  slots: PreviewSlotDiagnostic[];
57
73
  }
@@ -3,16 +3,15 @@ import type { DomRenderer } from '../renderers/domRenderer';
3
3
  export interface PreviewPendingOverlayViewModel {
4
4
  pendingState: PreviewPendingState | null;
5
5
  runtimeState: PreviewRuntimeState;
6
- isBuffering: boolean;
7
6
  }
8
7
  export declare class PreviewPendingOverlayRenderer implements DomRenderer<PreviewPendingOverlayViewModel> {
9
8
  private container;
10
9
  private statusElement;
11
- private detailElement;
12
10
  private actionElement;
13
11
  private readonly onRetry;
14
12
  constructor(onRetry: () => void);
15
13
  mount(container: HTMLElement): void;
16
14
  update(viewModel: PreviewPendingOverlayViewModel): void;
15
+ private resolveLoadingStatusText;
17
16
  destroy(): void;
18
17
  }
@@ -0,0 +1,25 @@
1
+ import type { ActiveClipPlaybackInfo } from '../models/types';
2
+ import { type PreviewSlotRecoveryReason } from './previewSlotPolicy';
3
+ export interface PreviewRecoveryAttemptInput {
4
+ entry: ActiveClipPlaybackInfo;
5
+ desiredSource: string;
6
+ reason: PreviewSlotRecoveryReason;
7
+ previousRecoveryKey: string | null;
8
+ previousRetryCount: number;
9
+ maxImmediateRecoveryRetries: number;
10
+ recoveryTimeBucketMs: number;
11
+ isBackgroundPreload: boolean;
12
+ }
13
+ export type PreviewRecoveryAttemptResult = {
14
+ status: 'continue';
15
+ recoveryKey: string;
16
+ retryCount: number;
17
+ } | {
18
+ status: 'park';
19
+ message: string;
20
+ parkReason: 'retries-exhausted';
21
+ } | {
22
+ status: 'fail';
23
+ message: string;
24
+ };
25
+ export declare function resolveRecoveryAttempt(input: PreviewRecoveryAttemptInput): PreviewRecoveryAttemptResult;
@@ -0,0 +1,18 @@
1
+ import type { ActiveClipPlaybackInfo, PlayState } from '../models/types';
2
+ import type { PreviewSlotTarget } from './previewTrackPlanner';
3
+ export interface PreviewSlotLike {
4
+ role: 'current' | 'preload';
5
+ trackId: string;
6
+ entry: ActiveClipPlaybackInfo | null;
7
+ requestedPlayState: PlayState;
8
+ }
9
+ export interface TrackSlotPairLike<TSlot extends PreviewSlotLike = PreviewSlotLike> {
10
+ current: TSlot;
11
+ preload: TSlot;
12
+ }
13
+ export declare function slotHasEntry(slot: PreviewSlotLike, clipId?: string | null): boolean;
14
+ export declare function shouldSwapTrackSlotsForPlan(slots: TrackSlotPairLike, currentEntry: ActiveClipPlaybackInfo | null): boolean;
15
+ export declare function swapTrackSlotPair<TSlot extends PreviewSlotLike>(slots: TrackSlotPairLike<TSlot>): TrackSlotPairLike<TSlot>;
16
+ export declare function buildSlotKey(slot: Pick<PreviewSlotLike, 'trackId' | 'role'>): string;
17
+ export declare function buildDeferredPreloadTarget(slot: PreviewSlotLike, syncRequestId?: number): PreviewSlotTarget | null;
18
+ export declare function resolveSlotFromKey<TSlot extends PreviewSlotLike>(trackSlots: Map<string, TrackSlotPairLike<TSlot>>, slotKey: string): TSlot | null;
@@ -0,0 +1,41 @@
1
+ import type { PlayState } from '../models/types';
2
+ import type { PreviewSlotPhase } from './previewBackend';
3
+ export interface PreviewSlotRecoveryStateLike {
4
+ isActive: boolean;
5
+ phase: PreviewSlotPhase;
6
+ errorMessage: string | null;
7
+ retryCount: number;
8
+ recoveryKey: string | null;
9
+ forceRecover: boolean;
10
+ isLoading: boolean;
11
+ isResolvingSource: boolean;
12
+ isBuffering: boolean;
13
+ isSeeking: boolean;
14
+ consecutiveStalledCount: number;
15
+ expectedEmptiedEvents: number;
16
+ recoverableEventCount: number;
17
+ loadStartedSinceRecover: boolean;
18
+ }
19
+ export interface PreviewTrackedCurrentVideoSlotStatus {
20
+ failed: boolean;
21
+ resolving: boolean;
22
+ seeking: boolean;
23
+ mediaUnready: boolean;
24
+ ready: boolean;
25
+ }
26
+ export declare function resetSlotRecoveryTrackingState(slot: PreviewSlotRecoveryStateLike, forceRecover?: boolean): void;
27
+ export declare function clearSlotState(slot: PreviewSlotRecoveryStateLike): void;
28
+ export declare function parkPreloadSlotState(slot: PreviewSlotRecoveryStateLike): void;
29
+ export declare function settleResolvedSourceWithoutRecoveryState(slot: Pick<PreviewSlotRecoveryStateLike, 'isActive' | 'phase' | 'isResolvingSource' | 'isLoading' | 'isBuffering' | 'errorMessage'>): void;
30
+ export declare function finalizeAppliedSlotPhase(slot: Pick<PreviewSlotRecoveryStateLike, 'isActive' | 'isLoading' | 'isSeeking' | 'phase'>): void;
31
+ export declare function beginDeferredPreloadState(slot: Pick<PreviewSlotRecoveryStateLike, 'phase' | 'forceRecover' | 'isLoading' | 'isResolvingSource' | 'isSeeking' | 'isBuffering' | 'errorMessage'>, hasAssignedSource: boolean): void;
32
+ export declare function reconcileTrackedCurrentVideoSlotState(slot: PreviewSlotRecoveryStateLike, input: {
33
+ hasDesiredSource: boolean;
34
+ sourceMatched: boolean;
35
+ withinSeekThreshold: boolean;
36
+ readyState: number;
37
+ haveCurrentDataState: number;
38
+ }): PreviewTrackedCurrentVideoSlotStatus;
39
+ export declare function failSlotState(slot: Pick<PreviewSlotRecoveryStateLike, 'phase' | 'isLoading' | 'isResolvingSource' | 'isSeeking' | 'isBuffering' | 'errorMessage' | 'forceRecover' | 'expectedEmptiedEvents'>, errorMessage: string): void;
40
+ export declare function beginPreloadPrimingState(slot: Pick<PreviewSlotRecoveryStateLike, 'isActive' | 'isBuffering' | 'isSeeking'>): void;
41
+ export declare function beginSlotRecoveryState(slot: PreviewSlotRecoveryStateLike, playState: PlayState): void;
@@ -0,0 +1,59 @@
1
+ import type { ActiveClipPlaybackInfo, PlayState } from '../models/types';
2
+ import type { PreviewSlotPhase } from './previewBackend';
3
+ export type PreviewSlotRecoveryReason = 'reconcile' | 'stalled' | 'abort' | 'emptied' | 'error';
4
+ export interface ResolvedSlotSourceLike {
5
+ stableSourceUrl: string;
6
+ url: string;
7
+ objectUrl: string | null;
8
+ }
9
+ export interface ReusableBoundSlotSourceInput {
10
+ currentSourceKey: string;
11
+ nextSourceKey: string;
12
+ desiredSource: string | null;
13
+ stableSourceUrl: string | null;
14
+ objectUrl: string | null;
15
+ }
16
+ export interface PreviewSlotRecoveryNeedInput {
17
+ desiredSource: string;
18
+ configuredSource: string | null;
19
+ assignedSource: string | null;
20
+ forceRecover: boolean;
21
+ isActive: boolean;
22
+ isLoading: boolean;
23
+ isSeeking: boolean;
24
+ isBuffering: boolean;
25
+ phase: PreviewSlotPhase;
26
+ networkState: number | null;
27
+ readyState: number | null;
28
+ networkEmptyState: number;
29
+ haveCurrentDataState: number;
30
+ }
31
+ export interface PreviewSlotRecoveryNeedResult {
32
+ needsRecovery: boolean;
33
+ recoveryReason: string | null;
34
+ actualSource: string | null;
35
+ configuredSource: string | null;
36
+ shouldWarn: boolean;
37
+ }
38
+ export declare function buildRecoveryKey(entry: ActiveClipPlaybackInfo, desiredSource: string, reason: PreviewSlotRecoveryReason, recoveryTimeBucketMs: number): string;
39
+ export declare function getReusableBoundSlotSource(input: ReusableBoundSlotSourceInput): ResolvedSlotSourceLike | null;
40
+ export declare function doesSlotSourceMatchDesired(configuredSource: string | null, assignedSource: string | null, desiredSource: string | null): boolean;
41
+ export declare function evaluateSlotRecoveryNeed(input: PreviewSlotRecoveryNeedInput): PreviewSlotRecoveryNeedResult;
42
+ export declare function getSlotSeekThresholdSeconds(role: 'current' | 'preload', playbackRate: number): number;
43
+ export declare function shouldHardResetRecoveredSource(previousActualSource: string | null, reason: PreviewSlotRecoveryReason, retryCount: number): boolean;
44
+ export declare function buildCurrentSlotTarget(input: {
45
+ role: 'current' | 'preload';
46
+ entry: ActiveClipPlaybackInfo;
47
+ requestedPlayState: PlayState;
48
+ isActive: boolean;
49
+ playbackRate: number;
50
+ syncRequestId?: number;
51
+ }): {
52
+ role: 'current' | 'preload';
53
+ entry: ActiveClipPlaybackInfo;
54
+ playState: PlayState;
55
+ speed: number;
56
+ visible: boolean;
57
+ zIndex: number;
58
+ syncRequestId?: number;
59
+ };
@@ -0,0 +1,24 @@
1
+ import type { ActiveClipPlaybackInfo, TrackType } from '../models/types';
2
+ import type { TimelinePreviewSyncPayload } from './previewBackend';
3
+ export interface PreviewSlotTarget {
4
+ role: 'current' | 'preload';
5
+ entry: ActiveClipPlaybackInfo | null;
6
+ playState: TimelinePreviewSyncPayload['playState'];
7
+ speed: number;
8
+ visible: boolean;
9
+ zIndex: number;
10
+ syncRequestId?: number;
11
+ }
12
+ export interface PreviewTrackPlan {
13
+ trackId: string;
14
+ kind: TrackType;
15
+ order: number;
16
+ current: PreviewSlotTarget;
17
+ preload: PreviewSlotTarget;
18
+ }
19
+ export interface ExistingPreviewTrack {
20
+ trackId: string;
21
+ kind: TrackType;
22
+ order: number;
23
+ }
24
+ export declare function projectPreviewPlan(payload: TimelinePreviewSyncPayload, existingTracks: ExistingPreviewTrack[]): PreviewTrackPlan[];
@@ -17,6 +17,8 @@ interface PreviewTransformOverlayCallbacks {
17
17
  onFontSizeCommit?: (clipId: string, fontSize: number) => void;
18
18
  onRotationChange?: (clipId: string, rotation: number | null) => void;
19
19
  onRotationCommit?: (clipId: string, rotation: number) => void;
20
+ /** 文字片段预览变换框上开始拖拽/缩放/旋转(播放中时应暂停) */
21
+ onTextTransformInteractionStart?: () => void;
20
22
  }
21
23
  export declare class PreviewTransformOverlay {
22
24
  private frameElement;
@@ -4,8 +4,6 @@ import type { ResourceCacheManager } from '../resources/resourceCache';
4
4
  import type { PreviewPendingState, PreviewRuntimeState } from './previewBackend';
5
5
  import { type DiagnosticEmitInput, type DiagnosticsCenter } from '../../utils/diagnostics';
6
6
  interface TimelinePreviewSessionCallbacks {
7
- onBufferingStateChange?: (isBuffering: boolean) => void;
8
- onSourceLoadingChange?: (pending: number) => void;
9
7
  onSyncProcessed?: (syncRequestId?: number) => void;
10
8
  onRuntimeStateChange?: (state: PreviewRuntimeState) => void;
11
9
  onAspectRatioChange?: (aspectRatio: PreviewAspectRatio) => void;
@@ -14,6 +12,7 @@ interface TimelinePreviewSessionCallbacks {
14
12
  onTextFontSizeCommit?: (clipId: string, fontSize: number) => void;
15
13
  onTextRotationCommit?: (clipId: string, rotation: number) => void;
16
14
  onPendingPreviewRetry?: () => void;
15
+ onPauseIfPlaying?: () => void;
17
16
  }
18
17
  interface TimelinePreviewSessionDependencies {
19
18
  createMediaElement?: (kind: TrackType, role: 'current' | 'preload') => HTMLMediaElement;
@@ -53,9 +52,9 @@ export declare class TimelinePreviewSession {
53
52
  private readonly textPreviewEntries;
54
53
  private audioContext;
55
54
  private masterGainNode;
56
- private loadingCount;
57
- private isBuffering;
58
- private bufferingVisibleTimeoutId;
55
+ private loadingProbeTimeoutId;
56
+ private clockProbeTimeoutId;
57
+ private playbackGroupSuspended;
59
58
  private lastRuntimeSignature;
60
59
  private lastRuntimeState;
61
60
  private lastSettledSyncRequestId;
@@ -80,13 +79,14 @@ export declare class TimelinePreviewSession {
80
79
  private aspectRatioProbe;
81
80
  private aspectRatioProbeSrc;
82
81
  private aspectRatioProbeResolveToken;
83
- private isAspectRatioProbeLoading;
84
82
  private pendingState;
85
83
  private activeSyncRequestId;
86
84
  private isSyncProjecting;
87
85
  private readonly deferredPreloadSlotKeys;
88
86
  private deferredPreloadFlushScheduled;
87
+ private lastSyncedPlayState;
89
88
  constructor(callbacks?: TimelinePreviewSessionCallbacks, dependencies?: TimelinePreviewSessionDependencies);
89
+ private requestPauseIfPlaying;
90
90
  private emitDiagnostic;
91
91
  private buildSlotTraceData;
92
92
  hasPreview(): boolean;
@@ -114,11 +114,13 @@ export declare class TimelinePreviewSession {
114
114
  private resolvePlayableSlotSource;
115
115
  private emitRuntimeSourceDiagnostic;
116
116
  private getReusableResolvedSlotSource;
117
+ private getReusableBoundSlotSource;
117
118
  private slotNeedsRecovery;
118
119
  private recoverSlot;
119
120
  private finishSlotRecovery;
120
121
  private getSlotKey;
121
122
  private isActiveCurrentSlot;
123
+ private isTrackedActiveCurrentVideoSlot;
122
124
  private hasBlockingActiveCurrentSlot;
123
125
  private shouldDeferPreloadTarget;
124
126
  private shouldDeferPreloadRecovery;
@@ -140,6 +142,9 @@ export declare class TimelinePreviewSession {
140
142
  private failSlot;
141
143
  private configureAudioRouting;
142
144
  private syncCurrentSlot;
145
+ private resetSlotPlaybackProgressProbe;
146
+ private updateSlotPlaybackProgressState;
147
+ private maybeResumeRequestedPlayback;
143
148
  private preparePreloadSlot;
144
149
  private setSlotVisible;
145
150
  private getAudioContext;
@@ -153,9 +158,23 @@ export declare class TimelinePreviewSession {
153
158
  private tryResolveAutoAspectRatioFromSlot;
154
159
  private handleResolvedAutoAspectRatio;
155
160
  private refreshRuntimeState;
156
- private syncVisibleBufferingState;
157
- private clearBufferingVisibleTimeout;
161
+ private buildPreviewClockState;
162
+ private buildPreviewClockStateFromSlot;
163
+ private resolveClockBlockedReason;
164
+ private getPreviewClockSourceSlot;
165
+ private syncActivePlaybackGroupSuspension;
166
+ private getSlotMediaTimeMs;
167
+ private getSlotTimelineTime;
158
168
  private refreshPendingOverlay;
169
+ private buildPreviewLoadingState;
170
+ private evaluateTrackedCurrentVideoSlot;
171
+ private doesSlotSourceMatchDesired;
172
+ private getSlotSeekThresholdSeconds;
173
+ private finalizeSourceResolutionResult;
174
+ private syncLoadingProbe;
175
+ private syncClockProbe;
176
+ private clearLoadingProbe;
177
+ private clearClockProbe;
159
178
  private handlePreviewTransformChange;
160
179
  private refreshVisualLayout;
161
180
  private syncTextPreviewEntries;
@@ -1,5 +1,5 @@
1
1
  import type { TimeMs } from '../models/types';
2
- import type { PreviewPendingState, PreviewRuntimeState } from './previewBackend';
2
+ import type { PreviewLoadingState, PreviewPendingState, PreviewRuntimeState } from './previewBackend';
3
3
  export interface TimelinePendingPreviewState {
4
4
  mode: 'seek' | 'scrub';
5
5
  targetTime: TimeMs;
@@ -13,36 +13,26 @@ export interface TimelinePendingPreviewState {
13
13
  }
14
14
  interface TimelinePreviewStateControllerCallbacks {
15
15
  applyPendingState: (state: PreviewPendingState | null) => void;
16
- emitBufferingStateChange: (isBuffering: boolean) => void;
17
- emitSourceLoadingStateChange: () => void;
16
+ emitLoadingStateChange: (state: PreviewLoadingState) => void;
18
17
  resumePlayback: () => void;
19
18
  requestPreviewSync: () => void;
20
19
  }
21
20
  interface TimelinePreviewStateControllerOptions {
22
21
  callbacks: TimelinePreviewStateControllerCallbacks;
23
- pendingTimeoutMs?: number;
24
22
  failedOverlayDelayMs?: number;
25
23
  }
26
24
  export declare class TimelinePreviewStateController {
27
25
  private readonly callbacks;
28
- private readonly pendingTimeoutMs;
29
26
  private readonly failedOverlayDelayMs;
30
- private _previewSourceLoadingCount;
31
- private _previewBuffering;
32
- private _previewAwaitingMedia;
27
+ private _previewLoadingState;
33
28
  private _pendingPreviewState;
34
29
  private _nextPendingPreviewSyncRequestId;
35
30
  constructor(options: TimelinePreviewStateControllerOptions);
36
- get previewSourceLoadingCount(): number;
37
- set previewSourceLoadingCount(value: number);
38
- get previewBuffering(): boolean;
39
- set previewBuffering(value: boolean);
31
+ get previewLoadingState(): PreviewLoadingState;
40
32
  get pendingPreviewState(): TimelinePendingPreviewState | null;
41
33
  set pendingPreviewState(value: TimelinePendingPreviewState | null);
42
34
  get nextPendingPreviewSyncRequestId(): number;
43
35
  set nextPendingPreviewSyncRequestId(value: number);
44
- handleBufferingStateChange(isBuffering: boolean): void;
45
- handleSourceLoadingChange(pending: number): void;
46
36
  handleRuntimeStateChange(state: PreviewRuntimeState): void;
47
37
  markSyncProcessed(syncRequestId?: number): void;
48
38
  beginPendingPreview(time: TimeMs, mode: 'seek' | 'scrub', resumePlayOnReady: boolean): void;
@@ -51,7 +41,8 @@ export declare class TimelinePreviewStateController {
51
41
  retryPendingPreview(): void;
52
42
  clearPendingPreviewState(): void;
53
43
  resetPreviewRuntimeState(): void;
54
- private ensurePendingPreviewTimeout;
44
+ private resolvePendingLoadingState;
45
+ private buildPlaybackLoadingOverlayState;
55
46
  private clearPendingPreviewTimeout;
56
47
  private scheduleFailedOverlay;
57
48
  private clearFailedOverlayTimeout;
@@ -58,7 +58,6 @@ export declare class TimelineManager {
58
58
  private previewStateController?;
59
59
  private previewPlaybackSuspendedByBuffering;
60
60
  private previewPlaybackAutoResume;
61
- private previewBufferingSuspendTimeoutId;
62
61
  private lastSelectedClipId;
63
62
  private previewAspectRatio;
64
63
  private readonly bodyViewportScrollListener;
@@ -106,10 +105,7 @@ export declare class TimelineManager {
106
105
  set runtimePreviewBackendOverride(value: Exclude<PreviewBackendType, 'auto'> | null);
107
106
  get activePreviewCallbackToken(): number;
108
107
  set activePreviewCallbackToken(value: number);
109
- private get previewSourceLoadingCount();
110
- private set previewSourceLoadingCount(value);
111
- get previewBuffering(): boolean;
112
- set previewBuffering(value: boolean);
108
+ private getPreviewLoadingState;
113
109
  private get pendingPreviewState();
114
110
  private set pendingPreviewState(value);
115
111
  get nextPendingPreviewSyncRequestId(): number;
@@ -160,6 +156,8 @@ export declare class TimelineManager {
160
156
  private getPlaybackTracksSnapshot;
161
157
  private buildPlaybackPlan;
162
158
  private hasTimelineVideoClip;
159
+ private playbackPlanHasActiveVideoClip;
160
+ private hasActiveTimelineVideoClip;
163
161
  private getPreviewAutoAspectRatioClipOrderMap;
164
162
  private getNextPreviewAutoAspectRatioOrder;
165
163
  private getAutoAspectRatioClip;
@@ -167,11 +165,16 @@ export declare class TimelineManager {
167
165
  private buildPreviewSyncSignature;
168
166
  private shouldSkipSteadyPlaybackPreviewSync;
169
167
  private syncPreviewSession;
168
+ private resolvePreviewSyncPlayState;
170
169
  private syncPreviewPlaybackStateIfNeeded;
170
+ private shouldUsePreviewClockPlayback;
171
+ private handlePreviewClockStateChange;
172
+ private commitPlaybackTimeFromPreviewClock;
173
+ private syncPreviewAfterPreviewClockCommitIfNeeded;
174
+ private resumeWallClockPlaybackIfPreviewClockUnavailable;
171
175
  private beginPendingPreview;
172
176
  updatePendingPreviewState(): void;
173
177
  private buildPreviewPendingState;
174
- private buildPreviewIndicatorState;
175
178
  private clearPendingPreviewState;
176
179
  private resetPreviewRuntimeState;
177
180
  private destroyPreviewSession;
@@ -218,8 +221,12 @@ export declare class TimelineManager {
218
221
  pause(): void;
219
222
  togglePlay(): void;
220
223
  private animate;
221
- private syncPlaybackClockToPreviewBuffering;
222
- private clearPreviewBufferingSuspendTimeout;
224
+ private cancelPlaybackAnimationFrame;
225
+ private emitPlayStateChangeEvent;
226
+ private suspendPlaybackForMediaPending;
227
+ private resumePlaybackAfterMediaPending;
228
+ private shouldSuspendPreviewPlaybackForLoadingState;
229
+ private syncPlaybackClockToPreviewLoadingState;
223
230
  /** 播放头不得超过全部片段的最大 endTime(与 canPlay / 播放结束对齐);无片段时不设上限(由别处处理空轨道) */
224
231
  private clampPlaybackSeekTime;
225
232
  setCurrentTime(time: TimeMs): void;
@@ -404,7 +404,7 @@ export interface HistoryState {
404
404
  past: Action[];
405
405
  future: Action[];
406
406
  }
407
- export type TimelineEvent = 'time_change' | 'play_state_change' | 'clip_added' | 'clip_removed' | 'clip_updated' | 'zoom_change' | 'history_change' | 'track_duration_change' | 'clip_selected' | 'selected_clip_change' | 'speed_change' | 'can_play_change' | 'buffering_state_change' | 'source_loading_change' | 'preview_aspect_ratio_change' | 'draft_loaded' | 'selection_change';
407
+ export type TimelineEvent = 'time_change' | 'play_state_change' | 'clip_added' | 'clip_removed' | 'clip_updated' | 'zoom_change' | 'history_change' | 'track_duration_change' | 'clip_selected' | 'selected_clip_change' | 'speed_change' | 'can_play_change' | 'loading_state_change' | 'preview_aspect_ratio_change' | 'draft_loaded' | 'selection_change';
408
408
  export interface TimeChangeData {
409
409
  time: TimeMs;
410
410
  }
@@ -437,12 +437,12 @@ export interface ClipRemovedEventData {
437
437
  export interface CanPlayChangeData {
438
438
  canPlay: boolean;
439
439
  }
440
- export interface BufferingStateChangeData {
441
- isBuffering: boolean;
442
- }
443
- export interface SourceLoadingChangeData {
440
+ export type LoadingStateStatus = 'idle' | 'loading' | 'ready' | 'failed';
441
+ export type LoadingStateReason = 'syncing' | 'resolving-source' | 'seeking' | 'media-pending' | 'failed' | null;
442
+ export interface LoadingStateChangeData {
443
+ status: LoadingStateStatus;
444
444
  isLoading: boolean;
445
- pending: number;
445
+ reason: LoadingStateReason;
446
446
  }
447
447
  export interface PreviewAspectRatioChangeData {
448
448
  aspectRatio: PreviewAspectRatio;