@linker-design-plus/timeline-track 2.0.24 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,24 +2,7 @@
2
2
 
3
3
  基于 TypeScript、Konva 和 Vue demo 的时间线编辑组件库,对外以 `TimelineManager` 为统一入口,提供多轨片段编辑、拖拽/拉伸/分割、撤销重做、选中态同步、封面渲染和预览挂载能力。时间轴主体仍由 Konva 驱动,播放指针使用独立的 SVG/DOM 覆盖层渲染。
4
4
 
5
- ## 当前状态
6
-
7
- - 主线重构已经完成,仓库已从“双轨实现 + 大量兼容层”收敛到单一轨道组件 `Track`
8
- - `TimelineManager` 仍然是 façade,但内部已经拆分为 `stores`、`commands`、`controllers`、`tracks`、`presentation`
9
- - 当前发布包实际内置的预览实现是 `DomPreviewBackend`
10
- - `previewBackend` 配置仍然保留,但当前实现会统一解析到 `dom`,不要把 `canvas` / `auto` 视为已交付能力
11
- - 仓库内保留了较完整的回归测试,适合继续做功能收敛和架构瘦身
12
-
13
- ## 审计结论
14
-
15
- 本轮已完成一次针对仓库现状的维护梳理,并同步修正了以下残留:
16
-
17
- - 修复了 3 处测试残留,使 `pnpm test` 与 `pnpm exec tsc -p tsconfig.json --noEmit` 恢复通过
18
- - 清理了 `pixi` 预览类型和 demo 入口中的过期选项
19
- - 把 README 与重构路线文档改为“当前真实状态”,不再把未交付的 canvas 预览写成已完成能力
20
- - 把 `build` 脚本改为直接调用本地 `tsc`,避免 `npm run build:types` 带来的环境告警
21
-
22
- 更细的维护记录见 [docs/maintenance-audit.md](./docs/maintenance-audit.md)。
5
+ 当前版本已经收敛到单一 `Track` 实现。预览侧真实交付的 backend 只有 `DomPreviewBackend`;`previewBackend` 配置仍保留兼容输入,但运行时会统一解析到 `dom`。
23
6
 
24
7
  ## 安装
25
8
 
@@ -66,35 +49,17 @@ timeline.on('history_change', (_event, data) => {
66
49
  });
67
50
  ```
68
51
 
69
- ### 预览资源缓存
70
-
71
- 预览资源缓存默认关闭。开启后,可被前端 `fetch` 读取且不超过 100MB 的 `http:` / `https:` 音视频文件会被缓存到浏览器存储中,用于后续预览播放。默认策略为 30 天 TTL、10GB 应用级容量上限、优先 OPFS,并在 OPFS 不可用时降级到 IndexedDB Blob。缓存失败会自动回退原始媒体 URL。
72
-
73
- ```ts
74
- const timeline = new TimelineManager({
75
- container,
76
- previewBackend: 'dom',
77
- resourceCache: {
78
- enabled: true,
79
- resolveMode: 'prefer-fast-start'
80
- }
81
- });
82
- ```
83
-
84
- 第一版不会缓存 HLS manifest / 分片,也不会缓存超过 100MB 的媒体文件。
85
-
86
52
  ## 核心能力
87
53
 
88
- - 多轨时间线编辑,支持视频轨和音频轨
54
+ - 多轨时间线编辑,支持视频轨、音频轨和文本片段
89
55
  - 片段拖拽、拉伸、分割、跨轨移动和重叠避让
90
- - 单一选中态与批量拖拽
56
+ - 多选、框选与批量拖拽
91
57
  - 播放头、缩放、滚动和时间同步
92
58
  - 撤销 / 重做 / 历史变更通知
93
- - 缩略图提供器与异步封面加载
94
- - 预览容器挂载、预览比例同步和当前激活片段解析
95
- - 草稿导入导出、音视频分离/恢复等编辑工作流
59
+ - 预览挂载、比例同步和当前激活片段解析
60
+ - 草稿导入导出、音视频分离 / 恢复等编辑工作流
96
61
 
97
- ## 主要 API
62
+ ## API 概览
98
63
 
99
64
  常用方法:
100
65
 
@@ -104,30 +69,30 @@ const timeline = new TimelineManager({
104
69
  - `setSpeed(speed)` / `getSpeed()`
105
70
  - `addClip(config)` / `addClips(configs)`
106
71
  - `updateClip(clipId, updates)` / `removeClip(clipId)`
107
- - `selectClip(clipId)` / `clearSelection()` / `getSelectedClip()`
72
+ - `selectClip(clipId)` / `setSelection(ids)` / `clearSelection()`
108
73
  - `splitCurrentClip()` / `removeClipGaps()`
109
- - `getRenderedHeight()`
110
74
  - `undo()` / `redo()` / `clearHistory()`
111
75
  - `attachPreview(containerOrConfig)` / `detachPreview()`
112
- - `exportTimeline()` / `loadDraft(data)`
76
+ - `exportTimeline()` / `importTimeline(data)`
113
77
 
114
- 快捷键配置:
78
+ 配音配置面板:
115
79
 
116
- - `keyboardShortcuts: false` 可彻底关闭快捷键
117
- - `keyboardShortcuts.bindings` 可按动作覆盖默认键位
118
- - 时间轴挂载后默认在当前页面生效
80
+ - `attachClipConfig(container, options)` 返回 `TimelineClipConfigController`
81
+ - 内建 `generateVoiceBatch` 流程会自动展示 / 关闭“配音生成中”全屏 loading
82
+ - 外部自定义配音流程也可以手动调用 `controller.showVoiceGenerationLoading()` 和 `controller.hideVoiceGenerationLoading()`
119
83
 
120
- 默认快捷键:
84
+ ```ts
85
+ const controller = timeline.attachClipConfig(clipConfigContainer, {
86
+ voiceCatalog
87
+ });
121
88
 
122
- - 播放 / 暂停:`Space`
123
- - 删除片段:`Delete` / `Backspace`
124
- - 复制片段:`Mod+C`
125
- - 剪切片段:`Mod+X`
126
- - 粘贴片段:`Mod+V`
127
- - 分离 / 还原音频:`Mod+Alt+A`
128
- - 分割片段:`Mod+B`
129
- - 撤销:`Mod+Z`
130
- - 还原:`Mod+Shift+Z`,Windows 另支持 `Ctrl+Y`
89
+ controller.showVoiceGenerationLoading();
90
+ try {
91
+ await customGenerateVoice();
92
+ } finally {
93
+ controller.hideVoiceGenerationLoading();
94
+ }
95
+ ```
131
96
 
132
97
  常用事件:
133
98
 
@@ -137,83 +102,30 @@ const timeline = new TimelineManager({
137
102
  - `clip_added`
138
103
  - `clip_removed`
139
104
  - `clip_updated`
140
- - `clip_selected`
141
105
  - `selected_clip_change`
142
106
  - `history_change`
143
107
  - `track_duration_change`
144
108
  - `buffering_state_change`
145
109
  - `source_loading_change`
146
110
 
147
- ## 关键类型
148
-
149
- ```ts
150
- interface ClipConfig {
151
- id?: string;
152
- type?: 'video' | 'audio';
153
- externalId?: string;
154
- src: string;
155
- name: string;
156
- isMuted?: boolean;
157
- startTime?: number;
158
- duration: number;
159
- startTimeAtSource?: number;
160
- endTimeAtSource?: number;
161
- sourceDuration?: number;
162
- thumbnails?: string[];
163
- style?: Record<string, any>;
164
- visualTransform?: {
165
- x: number;
166
- y: number;
167
- scale: number;
168
- };
169
- trackId?: string;
170
- volume?: number;
171
- }
172
- ```
173
-
174
- ## 预览实现说明
111
+ ## 兼容与边界
175
112
 
176
- - 当前代码路径实际只会创建 `DomPreviewBackend`
177
- - `previewBackend` 字段被保留,目的是不给后续预览实现扩展制造额外 breaking change
178
- - 如果你在业务接入层看到 `canvas` / `auto` 配置,请把它当作保留参数,而不是当前版本的有效能力
113
+ - 当前代码路径只会创建 `DomPreviewBackend`
114
+ - `canvas` / `auto` 仍可作为兼容输入传入,但不应视为已交付能力
115
+ - 挂载预览且当前时间命中视频片段时,时间轴会消费预览视频的真实媒体时钟
116
+ - 空白段、纯音频、文本或未挂载预览时,播放仍回退到 `TimelineManager` 的 wall-clock 推进
179
117
 
180
- ## 架构概览
118
+ ## 预览资源缓存
181
119
 
182
- ```mermaid
183
- flowchart TB
184
- API["Public API\nsrc/index.ts"] --> FACADE["TimelineManager\nsrc/core/facade/timelineManager.ts"]
185
- FACADE --> STORES["Stores\nTimeline / Selection / Playback / Viewport"]
186
- FACADE --> COMMANDS["Commands\nTimelineCommands"]
187
- FACADE --> CONTROLLERS["Controllers\nworkflow / selection / duration / playback / events"]
188
- FACADE --> TRACKS["Tracks\nTrackManager / Bridge / Collection"]
189
- FACADE --> PRESENTATION["Presentation\nTimelinePresentationAdapter"]
190
- FACADE --> PREVIEW["Preview\nDomPreviewBackend"]
191
- FACADE --> COMPONENTS["Presentation Views\nTimeline / Track / Clip / Panels / ManagedPlayhead"]
192
- ```
120
+ 预览资源缓存默认关闭。开启后,前端可直接 `fetch` 的音视频资源会被缓存到浏览器存储中,用于后续预览复用;缓存失败时会自动回退原始媒体 URL。完整边界和配置说明见 [docs/resource-cache.md](./docs/resource-cache.md)。
193
121
 
194
- 当前最值得关注的热点:
195
-
196
- - `src/core/facade/timelineManager.ts` 仍然超过 4k 行,是后续最重要的瘦身目标
197
- - `src/components/track/Track.tsx` 内部还保留一层 `legacy` 交互镜像,用于兼容旧拖拽状态
198
- - `src/utils/rendering/KonvaUtils.ts` 仍偏大,适合继续拆分为更小的渲染工具模块
199
- - `demo/App.vue` 既承担演示又承担诊断面板职责,维护成本偏高
200
-
201
- ## 目录结构
202
-
203
- ```text
204
- src/
205
- components/ 时间轴视图、SVG/DOM 覆盖层与交互 helper
206
- core/ stores / commands / controllers / facade / models
207
- utils/ 渲染、时间和日志工具
208
- styles/ 样式入口
209
- index.ts 对外导出
210
- docs/
211
- interaction-model.md
212
- maintenance-audit.md
213
- refactor-roadmap.md
214
- demo/
215
- App.vue
216
- main.ts
122
+ ```ts
123
+ const timeline = new TimelineManager({
124
+ container,
125
+ resourceCache: {
126
+ enabled: true
127
+ }
128
+ });
217
129
  ```
218
130
 
219
131
  ## 开发
@@ -226,9 +138,11 @@ pnpm test
226
138
  pnpm exec tsc -p tsconfig.json --noEmit
227
139
  ```
228
140
 
229
- ## 相关文档
141
+ ## 文档
230
142
 
143
+ - [docs/README.md](./docs/README.md): 文档入口
144
+ - [docs/architecture-overview.md](./docs/architecture-overview.md): 当前架构、模块边界、运行链路
145
+ - [docs/implementation-notes.md](./docs/implementation-notes.md): selection、preview、resource cache、draft 的实现笔记
146
+ - [docs/resource-cache.md](./docs/resource-cache.md): 预览资源缓存的能力边界与配置
231
147
  - [docs/interaction-model.md](./docs/interaction-model.md): 指针交互分层和拖拽约束
232
- - [docs/refactor-roadmap.md](./docs/refactor-roadmap.md): 主线重构归档与后续收敛方向
233
- - [docs/maintenance-audit.md](./docs/maintenance-audit.md): 本轮仓库审计、验证结果和依赖建议
234
- - [docs/review-remediation-plan.md](./docs/review-remediation-plan.md): 基于审查结论的修复计划与阶段性落地建议
148
+ - [docs/preview-playback-recovery-flow.md](./docs/preview-playback-recovery-flow.md): 预览媒体时钟驱动播放、buffering 阻塞与恢复续播流程
@@ -105,6 +105,8 @@ export declare class ClipConfigPanel {
105
105
  setClip(clip: Clip | null): void;
106
106
  setSelectionState(selectionState: ClipConfigPanelSelectionState): void;
107
107
  setPreferredTab(tab: 'voice' | null): void;
108
+ showVoiceGenerationLoading(): void;
109
+ hideVoiceGenerationLoading(): void;
108
110
  setVoiceGenerationBusy(isBusy: boolean): void;
109
111
  destroy(): void;
110
112
  private render;
@@ -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
  }
@@ -13,4 +13,5 @@ export { TimelinePreviewStateController, type TimelinePendingPreviewState } from
13
13
  export { TimelineKeyboardShortcutsController, type TimelineKeyboardShortcutsControllerCallbacks } from './timelineKeyboardShortcutsController';
14
14
  export * from './timelineSelectionController';
15
15
  export * from './timelineTrackMutationController';
16
+ export { TimelineClipConfigController } from './timelineClipConfigController';
16
17
  export { TimelineTrackInfoPanelController } from './timelineTrackInfoPanelController';
@@ -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;
@@ -29,15 +27,31 @@ export interface TimelinePreviewBackendCallbacks {
29
27
  /** 预览层文字交互(点击字幕或文字变换控件)时若时间线处于播放中,用于请求暂停 */
30
28
  onPauseIfPlaying?: () => void;
31
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;
36
+ }
32
37
  export interface PreviewPendingState {
33
- mode: 'seek' | 'scrub';
38
+ mode: 'seek' | 'scrub' | 'playback';
34
39
  targetTime: TimeMs;
35
- loadingPending: number;
36
- isBuffering: boolean;
40
+ loading: PreviewLoadingState;
37
41
  errorMessage: string | null;
38
42
  }
39
43
  export type PreviewRuntimePhase = 'idle' | 'awaiting-sync' | 'awaiting-media' | 'ready' | 'failed';
40
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
+ }
41
55
  export interface PreviewSlotDiagnostic {
42
56
  trackId: string;
43
57
  role: 'current' | 'preload';
@@ -52,8 +66,8 @@ export interface PreviewSlotDiagnostic {
52
66
  export interface PreviewRuntimeState {
53
67
  syncRequestId?: number;
54
68
  phase: PreviewRuntimePhase;
55
- loadingCount: number;
56
- isBuffering: boolean;
69
+ loading: PreviewLoadingState;
70
+ clock?: PreviewClockState;
57
71
  errorMessage: string | null;
58
72
  slots: PreviewSlotDiagnostic[];
59
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[];
@@ -18,6 +18,8 @@ export declare class TimelineClipConfigController {
18
18
  updateFromExternal(): void;
19
19
  destroy(): void;
20
20
  setPreferredTab(tab: 'voice' | null): void;
21
+ showVoiceGenerationLoading(): void;
22
+ hideVoiceGenerationLoading(): void;
21
23
  setVoiceGenerationBusy(isBusy: boolean): void;
22
24
  private buildSelectionState;
23
25
  private resolveUpdateTargets;
@@ -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;
@@ -54,9 +52,9 @@ export declare class TimelinePreviewSession {
54
52
  private readonly textPreviewEntries;
55
53
  private audioContext;
56
54
  private masterGainNode;
57
- private loadingCount;
58
- private isBuffering;
59
- private bufferingVisibleTimeoutId;
55
+ private loadingProbeTimeoutId;
56
+ private clockProbeTimeoutId;
57
+ private playbackGroupSuspended;
60
58
  private lastRuntimeSignature;
61
59
  private lastRuntimeState;
62
60
  private lastSettledSyncRequestId;
@@ -81,13 +79,13 @@ export declare class TimelinePreviewSession {
81
79
  private aspectRatioProbe;
82
80
  private aspectRatioProbeSrc;
83
81
  private aspectRatioProbeResolveToken;
84
- private isAspectRatioProbeLoading;
85
82
  private pendingState;
86
83
  private activeSyncRequestId;
87
84
  private isSyncProjecting;
88
85
  private readonly deferredPreloadSlotKeys;
89
86
  private deferredPreloadFlushScheduled;
90
87
  private lastSyncedPlayState;
88
+ private pendingClockAlignmentTargetTime;
91
89
  constructor(callbacks?: TimelinePreviewSessionCallbacks, dependencies?: TimelinePreviewSessionDependencies);
92
90
  private requestPauseIfPlaying;
93
91
  private emitDiagnostic;
@@ -117,11 +115,13 @@ export declare class TimelinePreviewSession {
117
115
  private resolvePlayableSlotSource;
118
116
  private emitRuntimeSourceDiagnostic;
119
117
  private getReusableResolvedSlotSource;
118
+ private getReusableBoundSlotSource;
120
119
  private slotNeedsRecovery;
121
120
  private recoverSlot;
122
121
  private finishSlotRecovery;
123
122
  private getSlotKey;
124
123
  private isActiveCurrentSlot;
124
+ private isTrackedActiveCurrentVideoSlot;
125
125
  private hasBlockingActiveCurrentSlot;
126
126
  private shouldDeferPreloadTarget;
127
127
  private shouldDeferPreloadRecovery;
@@ -143,6 +143,9 @@ export declare class TimelinePreviewSession {
143
143
  private failSlot;
144
144
  private configureAudioRouting;
145
145
  private syncCurrentSlot;
146
+ private resetSlotPlaybackProgressProbe;
147
+ private updateSlotPlaybackProgressState;
148
+ private maybeResumeRequestedPlayback;
146
149
  private preparePreloadSlot;
147
150
  private setSlotVisible;
148
151
  private getAudioContext;
@@ -156,9 +159,29 @@ export declare class TimelinePreviewSession {
156
159
  private tryResolveAutoAspectRatioFromSlot;
157
160
  private handleResolvedAutoAspectRatio;
158
161
  private refreshRuntimeState;
159
- private syncVisibleBufferingState;
160
- private clearBufferingVisibleTimeout;
162
+ private buildPreviewClockState;
163
+ private buildPreviewClockStateFromSlot;
164
+ private resolveClockBlockedReason;
165
+ private getPreviewClockSourceSlot;
166
+ private buildPreviewClockSourceCandidate;
167
+ private comparePreviewClockSourceCandidates;
168
+ private isPreviewClockCandidateAlignedToTarget;
169
+ private getPreviewClockTargetAlignmentThresholdMs;
170
+ private isPreviewClockSlotAlignedToPendingTarget;
171
+ private updatePendingClockAlignmentTarget;
172
+ private syncActivePlaybackGroupSuspension;
173
+ private getSlotMediaTimeMs;
174
+ private getSlotTimelineTime;
161
175
  private refreshPendingOverlay;
176
+ private buildPreviewLoadingState;
177
+ private evaluateTrackedCurrentVideoSlot;
178
+ private doesSlotSourceMatchDesired;
179
+ private getSlotSeekThresholdSeconds;
180
+ private finalizeSourceResolutionResult;
181
+ private syncLoadingProbe;
182
+ private syncClockProbe;
183
+ private clearLoadingProbe;
184
+ private clearClockProbe;
162
185
  private handlePreviewTransformChange;
163
186
  private refreshVisualLayout;
164
187
  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;