@linker-design-plus/timeline-track 2.0.4 → 2.0.5

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
@@ -1,29 +1,30 @@
1
1
  # @linker-design-plus/timeline-track
2
2
 
3
- 基于 TypeScript 和 Konva 的时间线编辑组件库,提供单一主入口 `TimelineManager`,支持多轨片段编辑、缩放、分割、撤销重做、封面渲染以及多轨音视频预览播放。
3
+ 基于 TypeScript、KonvaVue demo 的时间线编辑组件库,对外以 `TimelineManager` 为统一入口,提供多轨片段编辑、拖拽/拉伸/分割、撤销重做、选中态同步、封面渲染和预览挂载能力。
4
4
 
5
5
  ## 当前状态
6
6
 
7
- - 主线重构已完成,代码已收敛到单一轨道实现 `Track`
8
- - `TimelineManager` 仍是对外 façade,但内部职责已拆分到 store / commands / controller / adapter
9
- - 工具层和核心层已有回归测试覆盖,当前保持全量自动化回归
10
- - 交互约束与重构路线分别见 [docs/interaction-model.md](./docs/interaction-model.md) [docs/refactor-roadmap.md](./docs/refactor-roadmap.md)
7
+ - 主线重构已经完成,仓库已从“双轨实现 + 大量兼容层”收敛到单一轨道组件 `Track`
8
+ - `TimelineManager` 仍然是 façade,但内部已经拆分为 `stores`、`commands`、`controllers`、`tracks`、`presentation`
9
+ - 当前发布包实际内置的预览实现是 `DomPreviewBackend`
10
+ - `previewBackend` 配置仍然保留,但当前实现会统一解析到 `dom`,不要把 `canvas` / `auto` 视为已交付能力
11
+ - 仓库内保留了较完整的回归测试,适合继续做功能收敛和架构瘦身
11
12
 
12
- ## 能力概览
13
+ ## 审计结论
13
14
 
14
- - 多轨时间线编辑,支持视频轨和音频轨
15
- - 片段拖拽、拉伸、分割、跨轨移动、自动避让重叠
16
- - 单一选中态,避免多个 clip 同时处于最终选中状态
17
- - 播放头、滚动、缩放和时间同步
18
- - 撤销 / 重做 / 历史变更通知
19
- - 缩略图提供器与异步封面加载
20
- - 多轨视频层级叠加预览与多轨音频混合播放
21
- - 预览容器挂载、buffering 状态与预览比例同步
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)。
22
23
 
23
24
  ## 安装
24
25
 
25
26
  ```bash
26
- npm install @linker-design-plus/timeline-track
27
+ pnpm add @linker-design-plus/timeline-track konva
27
28
  ```
28
29
 
29
30
  ## 快速开始
@@ -36,29 +37,9 @@ const preview = document.getElementById('preview-container') as HTMLDivElement;
36
37
 
37
38
  const timeline = new TimelineManager({
38
39
  container,
39
- debug: true,
40
40
  zoom: 100,
41
41
  currentTime: 0,
42
- previewBackend: 'dom',
43
- previewSourceResolver: (clip) => {
44
- if (clip.src.endsWith('.mp4')) {
45
- return {
46
- url: clip.src,
47
- mimeType: 'video/mp4',
48
- kind: 'mp4'
49
- };
50
- }
51
-
52
- if (clip.src.endsWith('.m3u8')) {
53
- return {
54
- url: clip.src,
55
- mimeType: 'application/vnd.apple.mpegurl',
56
- kind: 'hls-fmp4'
57
- };
58
- }
59
-
60
- return null;
61
- }
42
+ previewBackend: 'dom'
62
43
  });
63
44
 
64
45
  timeline.attachPreview(preview);
@@ -72,72 +53,46 @@ await timeline.addClip({
72
53
  x: 0.5,
73
54
  y: 0.5,
74
55
  scale: 1
75
- },
76
- thumbnails: ['https://example.com/thumb-1.jpg']
56
+ }
77
57
  });
78
58
 
79
59
  timeline.on('history_change', (_event, data) => {
80
- console.log('canUndo', data.canUndo, 'canRedo', data.canRedo);
60
+ console.log(data.canUndo, data.canRedo);
81
61
  });
82
-
83
- timeline.play();
84
62
  ```
85
63
 
86
- ## 缩略图提供器
87
-
88
- `ThumbnailProvider` 使用 `getThumbnails(clip)`,返回 `string[]` 或 `Promise<string[]>`。
89
- 音频 clip 默认不会使用缩略图渲染,`thumbnailProvider` 主要作用于视频 clip。
64
+ ## 核心能力
90
65
 
91
- ```ts
92
- import { TimelineManager, type ThumbnailProvider } from '@linker-design-plus/timeline-track';
93
-
94
- const thumbnailProvider: ThumbnailProvider = {
95
- async getThumbnails(clip) {
96
- return [
97
- `https://example.com/thumbnails/${clip.id}/0.jpg`,
98
- `https://example.com/thumbnails/${clip.id}/1.jpg`
99
- ];
100
- }
101
- };
102
-
103
- const timeline = new TimelineManager({
104
- container,
105
- thumbnailProvider
106
- });
107
-
108
- timeline.setThumbnailProvider(thumbnailProvider);
109
- await timeline.refreshAllClipThumbnails();
110
- ```
111
-
112
- ## 对外主 API
66
+ - 多轨时间线编辑,支持视频轨和音频轨
67
+ - 片段拖拽、拉伸、分割、跨轨移动和重叠避让
68
+ - 单一选中态与批量拖拽
69
+ - 播放头、缩放、滚动和时间同步
70
+ - 撤销 / 重做 / 历史变更通知
71
+ - 缩略图提供器与异步封面加载
72
+ - 预览容器挂载、预览比例同步和当前激活片段解析
73
+ - 草稿导入导出、音视频分离/恢复等编辑工作流
113
74
 
114
- ### `TimelineManager`
75
+ ## 主要 API
115
76
 
116
77
  常用方法:
117
78
 
118
79
  - `play()` / `pause()` / `togglePlay()`
119
80
  - `setCurrentTime(time)` / `getCurrentTime()`
120
- - `setZoom(zoom)` / `getZoom()`
81
+ - `setZoom(zoom)` / `fitZoom()`
121
82
  - `setSpeed(speed)` / `getSpeed()`
122
83
  - `addClip(config)` / `addClips(configs)`
123
- - `updateClip(clipId, updates)` / `removeClip(clipId)` / `removeClipsByExternalId(externalId)`
84
+ - `updateClip(clipId, updates)` / `removeClip(clipId)`
124
85
  - `selectClip(clipId)` / `clearSelection()` / `getSelectedClip()`
125
- - `canSeparateClipAudio(clipId)` / `separateClipAudio(clipId)` / `canRestoreClipAudio(clipId)` / `restoreClipAudio(clipId)`
126
- - `splitCurrentClip()` / `removeClipGaps()` / `fitZoom()`
86
+ - `splitCurrentClip()` / `removeClipGaps()`
127
87
  - `undo()` / `redo()` / `clearHistory()`
128
88
  - `attachPreview(containerOrConfig)` / `detachPreview()`
129
- - `getPreviewBackendType()`
130
- - `getCurrentActiveClips()` / `getActiveClipsAtTime(time)`
131
- - `getPreviewAspectRatio()` / `setPreviewAspectRatio({ width, height })` / `resetPreviewAspectRatioToAuto()`
132
- - `exportTimeline()`
89
+ - `exportTimeline()` / `loadDraft(data)`
133
90
 
134
91
  常用事件:
135
92
 
136
93
  - `time_change`
137
94
  - `play_state_change`
138
- - `speed_change`
139
95
  - `zoom_change`
140
- - `preview_aspect_ratio_change`
141
96
  - `clip_added`
142
97
  - `clip_removed`
143
98
  - `clip_updated`
@@ -146,10 +101,9 @@ await timeline.refreshAllClipThumbnails();
146
101
  - `history_change`
147
102
  - `track_duration_change`
148
103
  - `buffering_state_change`
149
- - `can_play_change`
150
104
  - `source_loading_change`
151
105
 
152
- ## 核心类型
106
+ ## 关键类型
153
107
 
154
108
  ```ts
155
109
  interface ClipConfig {
@@ -171,217 +125,68 @@ interface ClipConfig {
171
125
  y: number;
172
126
  scale: number;
173
127
  };
174
- separatedAudioClipId?: string;
175
- separatedFromVideoClipId?: string;
128
+ trackId?: string;
129
+ volume?: number;
176
130
  }
177
131
  ```
178
132
 
179
- `type: 'audio'` 时,clip 会渲染为音频卡片样式:内置音频图标、装饰性波形和文件名,不展示视频缩略图。
180
- 视频 clip 可通过 `visualTransform` 控制预览中的归一化位置和等比缩放,默认值为 `{ x: 0.5, y: 0.5, scale: 1 }`。
181
- 视频 clip 分离原声后会写入 `separatedAudioClipId`,对应音频 clip 会写入 `separatedFromVideoClipId`。
182
- 导出时间线时会包含 `composition`、轨道 `order` / `isMuted`、clip `isMuted` 和视频 clip 的 `visualTransform`,便于后端按当前多轨预览语义做合成。
133
+ ## 预览实现说明
183
134
 
184
- ### 预览后端
135
+ - 当前代码路径实际只会创建 `DomPreviewBackend`
136
+ - `previewBackend` 字段被保留,目的是不给后续预览实现扩展制造额外 breaking change
137
+ - 如果你在业务接入层看到 `canvas` / `auto` 配置,请把它当作保留参数,而不是当前版本的有效能力
185
138
 
186
- `TimelineManager` 现在支持双预览后端:
139
+ ## 架构概览
187
140
 
188
- - `previewBackend: 'dom'`
189
- 当前默认后端,使用多媒体元素叠层完成多轨预览,兼容性最好。
190
- - `previewBackend: 'canvas'`
191
- 第一阶段的新预览后端,使用 Canvas2D + WebCodecs 进行视频解码与单画布合成。
192
- - `previewBackend: 'auto'`
193
- 在支持的浏览器中优先使用 `canvas`,否则自动回退到 `dom`。
194
-
195
- 配合 `canvas` / `auto` 后端,建议显式提供 `previewSourceResolver`,让编辑预览使用代理 MP4:
196
-
197
- ```ts
198
- const timeline = new TimelineManager({
199
- container,
200
- previewBackend: 'canvas',
201
- previewSourceResolver: (clip) => {
202
- if (!clip.src.endsWith('.mp4')) {
203
- return clip.src.endsWith('.m3u8')
204
- ? {
205
- url: clip.src,
206
- mimeType: 'application/vnd.apple.mpegurl',
207
- kind: 'hls-fmp4'
208
- }
209
- : null;
210
- }
211
-
212
- return {
213
- url: clip.src,
214
- mimeType: 'video/mp4',
215
- kind: 'mp4'
216
- };
217
- }
218
- });
141
+ ```mermaid
142
+ flowchart TB
143
+ API["Public API\nsrc/index.ts"] --> FACADE["TimelineManager\nsrc/core/facade/timelineManager.ts"]
144
+ FACADE --> STORES["Stores\nTimeline / Selection / Playback / Viewport"]
145
+ FACADE --> COMMANDS["Commands\nTimelineCommands"]
146
+ FACADE --> CONTROLLERS["Controllers\nworkflow / selection / duration / playback / events"]
147
+ FACADE --> TRACKS["Tracks\nTrackManager / Bridge / Collection"]
148
+ FACADE --> PRESENTATION["Presentation\nTimelinePresentationAdapter"]
149
+ FACADE --> PREVIEW["Preview\nDomPreviewBackend"]
150
+ FACADE --> COMPONENTS["Konva Views\nTimeline / Track / Clip / Playhead / Panels"]
219
151
  ```
220
152
 
221
- 当前阶段限制:
222
-
223
- - `canvas` 后端仅面向 Chrome / Edge 桌面端
224
- - 视频预览源支持 MP4,以及 `EXT-X-MAP + m4s` 形式的 fMP4 HLS
225
- - 暂不支持基于 TS 分片的 HLS,也不支持 DRM 流
226
- - Firefox / Safari 会自动回退到 `dom` 后端
153
+ 当前最值得关注的热点:
227
154
 
228
- ## 当前代码架构
155
+ - `src/core/facade/timelineManager.ts` 仍然超过 4k 行,是后续最重要的瘦身目标
156
+ - `src/components/track/Track.tsx` 内部还保留一层 `legacy` 交互镜像,用于兼容旧拖拽状态
157
+ - `src/utils/rendering/KonvaUtils.ts` 仍偏大,适合继续拆分为更小的渲染工具模块
158
+ - `demo/App.vue` 既承担演示又承担诊断面板职责,维护成本偏高
229
159
 
230
- ### 入口与 façade
231
-
232
- - [src/index.ts](./src/index.ts)
233
- - [src/core/facade/timelineManager.ts](./src/core/facade/timelineManager.ts)
234
-
235
- ### 组件层
236
-
237
- - [src/components/timeline/Timeline.tsx](./src/components/timeline/Timeline.tsx)
238
- - [src/components/track/Track.tsx](./src/components/track/Track.tsx)
239
- - [src/components/track/Clip.tsx](./src/components/track/Clip.tsx)
240
- - [src/components/timeline/Playhead.tsx](./src/components/timeline/Playhead.tsx)
241
- - [src/components/panel/TrackInfoPanel.tsx](./src/components/panel/TrackInfoPanel.tsx)
242
-
243
- ### 核心状态与命令
244
-
245
- - `timelineStore` / `selectionStore` / `playbackStore` / `viewportStore`
246
- - `timelineCommands`
247
- - `timelineHistoryRecorder` / `timelineHistoryExecutor`
248
-
249
- ### façade 已拆出的控制器与适配层
250
-
251
- - `timelineEventDispatcher`
252
- - `timelineTrackInfoPanelController`
253
- - `timelinePlaybackResolver`
254
- - `timelinePreviewSession`
255
- - `timelineSelectionController`
256
- - `timelineDurationController`
257
- - `timelineClipEventController`
258
- - `timelinePresentationAdapter`
259
- - `timelineTrackBridge`
260
- - `timelineTrackCollection`
261
- - `timelineTrackLayout`
262
-
263
- ### 工具层
264
-
265
- - `clipVisualRenderer`
266
- - `clipCoverRenderer`
267
- - `timelineGridDrawing`
268
- - `timeUtils`
269
- - `KonvaUtils`
270
-
271
- ### 目录结构
160
+ ## 目录结构
272
161
 
273
162
  ```text
274
163
  src/
275
- components/ Konva 组件和交互 helper
276
- core/ 状态、命令、controller、façade、类型
277
- utils/ 渲染与通用工具
164
+ components/ Konva 组件与交互 helper
165
+ core/ stores / commands / controllers / facade / models
166
+ utils/ 渲染、时间和日志工具
278
167
  styles/ 样式入口
279
168
  index.ts 对外导出
280
169
  docs/
281
170
  interaction-model.md
171
+ maintenance-audit.md
282
172
  refactor-roadmap.md
283
173
  demo/
284
174
  App.vue
285
175
  main.ts
286
176
  ```
287
177
 
288
- ## 架构图级别说明
289
-
290
- ### 分层关系图
291
-
292
- ```mermaid
293
- flowchart TB
294
- API["Public API\nsrc/index.ts"] --> FACADE["TimelineManager façade\nsrc/core/facade/timelineManager.ts"]
295
-
296
- FACADE --> STORES["Stores\nTimelineStore / PlaybackStore / SelectionStore / ViewportStore"]
297
- FACADE --> COMMANDS["Commands\nTimelineCommands"]
298
- FACADE --> CONTROLLERS["Controllers\nclip workflow / track mutation / playback / selection / duration / event dispatcher"]
299
- FACADE --> HISTORY["History\nHistoryManager / Recorder / Executor"]
300
- FACADE --> TRACKS["Tracks\nTrackManager / TimelineTrackBridge / TimelineTrackCollection"]
301
- FACADE --> PRESENTATION["Presentation\nTimelinePresentationAdapter"]
302
- FACADE --> PREVIEW["Preview Backend\nDomPreviewBackend / CanvasPreviewBackend"]
303
- FACADE --> COMPONENTS["Konva Views\nTimeline / Track / Clip / Playhead / TrackInfoPanel"]
304
-
305
- COMPONENTS --> INTERACTION["Interaction\nglobalPointerDragSession / trackInteractionState"]
306
- COMPONENTS --> UTILS["Utils\ntime / rendering / logging"]
307
-
308
- TRACKS --> COMPONENTS
309
- COMMANDS --> STORES
310
- CONTROLLERS --> STORES
311
- CONTROLLERS --> HISTORY
312
- CONTROLLERS --> TRACKS
313
- PRESENTATION --> COMPONENTS
314
- PREVIEW --> STORES
315
- ```
316
-
317
- ### 运行时职责图
318
-
319
- ```mermaid
320
- flowchart LR
321
- USER["用户操作\n拖拽 / 拉伸 / 分割 / 播放"] --> VIEW["组件层\nTrack / Clip / Timeline"]
322
- VIEW --> BRIDGE["TrackBridge / 回调桥接"]
323
- BRIDGE --> WORKFLOW["Controllers\n工作流编排"]
324
- WORKFLOW --> COMMANDS2["Commands\n领域规则与变更计划"]
325
- COMMANDS2 --> STATE["Stores + TrackCollection\n时间轴状态与轨道集合"]
326
- WORKFLOW --> HISTORY2["History\n记录 undo / redo"]
327
- WORKFLOW --> PREVIEW2["Preview Session\n同步多轨预览"]
328
- STATE --> PRESENTATION2["PresentationAdapter\n把状态转换为渲染输入"]
329
- PRESENTATION2 --> VIEW
330
- WORKFLOW --> EVENTS["EventDispatcher\n对外事件通知"]
331
- ```
332
-
333
- ### 核心分层说明
334
-
335
- - `src/index.ts`:对外导出层,暴露 `TimelineManager`、组件、`HistoryManager`、`TrackManager` 与模型类型。
336
- - `src/core/facade/timelineManager.ts`:系统总入口,负责装配 store、commands、controllers、history、preview backend 与 Konva 视图。
337
- - `src/core/stores/`:保存当前时间、播放状态、选中态、视口等最小状态源。
338
- - `src/core/commands/`:封装纯领域规则,例如选中、查找 clip、跨轨移动准备、轨道放置规划。
339
- - `src/core/controllers/`:把用户行为编排成完整工作流,处理副作用、事件派发、预览同步与轨道/clip 变更。
340
- - `src/core/tracks/`:管理轨道集合、轨道桥接和轨道级别操作,是 façade 与 `Track` 组件之间的中间层。
341
- - `src/core/presentation/`:把核心状态适配为视图输入,降低 façade 与 Konva 组件之间的耦合。
342
- - `src/components/`:Konva 视图层,负责时间轴、轨道、clip、滚动条、播放头和面板渲染。
343
- - `src/components/interaction/`:处理全局拖拽会话与交互状态机,保证拖拽移动/结束在窗口级别可靠收敛。
344
- - `src/core/history/`:记录命令结果,提供撤销/重做闭环。
345
- - `src/core/controllers/*Preview*`:抽象预览后端,支持 `dom` 与 `canvas` 两套多轨预览实现。
346
-
347
- ### 典型调用链路
348
-
349
- #### 1. Clip 拖拽/跨轨移动
350
-
351
- 1. 用户在 `Track` / `Clip` 上产生指针操作。
352
- 2. `trackInteractionState` 识别当前操作类型,`globalPointerDragSession` 保证拖拽期间的全局 move/end 监听。
353
- 3. `Track` 通过回调把变更意图交给 `TimelineTrackBridge` 与 façade。
354
- 4. façade 调用 `timelineTrackMutationController`、`timelineClipWorkflowController`、`TimelineCommands` 计算目标轨道、位置与合法性。
355
- 5. 轨道集合与 store 更新后,同步写入 history,并触发重绘、事件通知与预览刷新。
356
-
357
- #### 2. 播放与时间推进
358
-
359
- 1. `TimelineManager` 持有播放状态与当前时间。
360
- 2. `TimelinePlaybackResolver` 与预览 session 根据当前时间解析 active clips。
361
- 3. `DomPreviewBackend` 或 `CanvasPreviewBackend` 执行多轨音视频预览。
362
- 4. 时间变化通过 `TimelineEventDispatcher` 向外发布 `time_change`、`play_state_change` 等事件。
363
-
364
- ### 当前架构特点
365
-
366
- - 对外仍保持单一 façade:调用方只需面向 `TimelineManager`。
367
- - 内部已经拆成 store / commands / controllers / tracks / presentation,职责边界比早期单体 manager 更清晰。
368
- - UI 交互和领域规则分离:交互细节主要留在 `components`,领域决策集中在 `core`。
369
- - 预览后端可替换:预览能力通过 backend 抽象扩展,而不是直接耦合到时间轴视图。
370
- - 历史系统贯穿工作流:新增、删除、更新、跨轨移动都能进入 undo / redo 闭环。
371
-
372
178
  ## 开发
373
179
 
374
180
  ```bash
375
181
  pnpm install
376
- pnpm run dev
377
- pnpm run build
182
+ pnpm dev
183
+ pnpm build
378
184
  pnpm test
379
185
  pnpm exec tsc -p tsconfig.json --noEmit
380
186
  ```
381
187
 
382
- ## 浏览器支持
188
+ ## 相关文档
383
189
 
384
- - Chrome
385
- - Edge
386
- - Firefox(`dom` 后端)
387
- - Safari(`dom` 后端)
190
+ - [docs/interaction-model.md](./docs/interaction-model.md): 指针交互分层和拖拽约束
191
+ - [docs/refactor-roadmap.md](./docs/refactor-roadmap.md): 主线重构归档与后续收敛方向
192
+ - [docs/maintenance-audit.md](./docs/maintenance-audit.md): 本轮仓库审计、验证结果和依赖建议
@@ -1,5 +1,6 @@
1
1
  import Konva from 'konva';
2
2
  import { Clip as ClipType, ClipStateUpdate, TrackConfig, TimeMs, Theme, TrackType } from '../../core/models';
3
+ import { type TrackPointerOperation } from './trackInteractionState';
3
4
  import type { MultiDragMoveRequest } from '../../core/tracks/timelineTrackBridge';
4
5
  export declare class Track {
5
6
  private static readonly DEFAULT_DRAG_ACTIVATION_THRESHOLD;
@@ -26,6 +27,7 @@ export declare class Track {
26
27
  private multiDragOriginalPositions;
27
28
  private promotedClipParents;
28
29
  private interactionState;
30
+ private legacyInteractionSnapshot?;
29
31
  private isVisualUpdate;
30
32
  private onClipUpdate;
31
33
  private onClipAdd;
@@ -55,8 +57,37 @@ export declare class Track {
55
57
  private handleGlobalPointerMove;
56
58
  private handleGlobalPointerEnd;
57
59
  private handleWindowBlur;
60
+ get hasDragMoved(): boolean;
61
+ set hasDragMoved(value: boolean);
62
+ get activePointerOperation(): TrackPointerOperation | null;
63
+ set activePointerOperation(value: TrackPointerOperation | null);
64
+ get originalClipsState(): ClipType[];
65
+ set originalClipsState(value: ClipType[]);
66
+ get nonDraggedClips(): ClipType[];
67
+ set nonDraggedClips(value: ClipType[]);
68
+ get snapCandidateClips(): ClipType[];
69
+ set snapCandidateClips(value: ClipType[]);
70
+ get dragStartY(): number;
71
+ set dragStartY(value: number);
72
+ get dragTargetTrackY(): number;
73
+ set dragTargetTrackY(value: number);
74
+ get crossTrackDragOffsetY(): number;
75
+ set crossTrackDragOffsetY(value: number);
76
+ get crossTrackDragStartX(): number;
77
+ set crossTrackDragStartX(value: number);
78
+ get dragStartScrollLeft(): number;
79
+ set dragStartScrollLeft(value: number);
80
+ get dragGestureStartClientX(): number | null;
81
+ set dragGestureStartClientX(value: number | null);
82
+ get dragGestureStartClientY(): number | null;
83
+ set dragGestureStartClientY(value: number | null);
84
+ get lastDragClientX(): number | null;
85
+ set lastDragClientX(value: number | null);
86
+ get lastDragClientY(): number | null;
87
+ set lastDragClientY(value: number | null);
58
88
  constructor(layer: Konva.Layer, config: TrackConfig, trackType: TrackType, zoom: number, trackY: number, trackHeight: number, theme: Theme, onClipUpdate: (clip: ClipType, originalClip?: ClipType, clipUpdates?: ClipStateUpdate[]) => void, onClipAdd: (clip: ClipType) => void, onClipRemove: (clipId: string) => void, onClipSplit: (clip1: ClipType, clip2: ClipType) => void, onClipSelect: (clip: ClipType) => void, onTimeJump: (time: TimeMs) => void, onHorizontalDragAutoScroll?: (nextScrollLeft: number) => number, onClipOverlap?: (clip: ClipType, currentTrackId: string) => void, onClipCrossTrackPreview?: (clip: ClipType, targetTrackY: number, currentTrackId: string) => 'self' | 'external' | 'clear', onClipCrossTrack?: (clip: ClipType, originalClip: ClipType | null, targetTrackY: number, currentTrackId: string) => boolean | void, onClearDropPreview?: () => void, onClearSelection?: () => void, onSnapGuideChange?: (guideTime: TimeMs | null) => void, onClipToggleSelection?: (clipId: string) => void, onSetSingleSelection?: (clipId: string) => void, getMultiDragClipIds?: (clipId: string) => string[] | null, onMultiDragMove?: (request: MultiDragMoveRequest) => boolean | void, onMultiDragInteractionEnd?: () => void, dragActivationThreshold?: number, enableClipSnap?: boolean, clipSnapThreshold?: number);
59
89
  private initClips;
90
+ private getLegacyInteractionSnapshot;
60
91
  private ensureDropPreviewGroup;
61
92
  private createDropPreviewRect;
62
93
  private showDropPreview;
@@ -107,10 +138,9 @@ export declare class Track {
107
138
  render(shouldBatchDraw?: boolean): void;
108
139
  private getTrackBackgroundFill;
109
140
  getClips(): ClipType[];
110
- getSelectedClip(): ClipType | null;
141
+ getPrimarySelectedClip(): ClipType | null;
111
142
  clearSelection(): void;
112
- updateSelectionVisual(selectedClipId: string | null): void;
113
- selectClip(clipId: string): void;
143
+ setSelection(clipIds: string[], preferredClipId?: string | null): void;
114
144
  private applySelectionVisual;
115
145
  private syncSelectionState;
116
146
  splitSelectedClip(time: TimeMs): void;
@@ -1,4 +1,4 @@
1
- import type { Clip, ClipConfig, PlayState, PreviewAspectRatio, Track, TrackInsertionPlacement, TrackType } from '../models/types';
1
+ import type { ClipConfig, PlayState, PreviewAspectRatio, Track, TrackInsertionPlacement, TrackType } from '../models/types';
2
2
  export interface DemoPreviewClipSnapshot extends ClipConfig {
3
3
  id: string;
4
4
  previewAutoOrder?: number;
@@ -17,7 +17,8 @@ export interface DemoPreviewRebuildSnapshot {
17
17
  zoom: number;
18
18
  playState: PlayState;
19
19
  previewAspectRatio: PreviewAspectRatio;
20
- selectedClipId: string | null;
20
+ selectedClipIds: string[];
21
+ selectedClipId?: string | null;
21
22
  isPreviewAttached: boolean;
22
23
  tracks: DemoPreviewTrackSnapshot[];
23
24
  }
@@ -27,7 +28,7 @@ interface DemoPreviewStateReader {
27
28
  getSpeed(): number;
28
29
  getZoom(): number;
29
30
  getPlayState(): PlayState;
30
- getSelectedClip(): Clip | null;
31
+ getSelectedClipIds(): string[];
31
32
  getTracks(): Track[];
32
33
  }
33
34
  interface DemoPreviewStateWriter extends DemoPreviewStateReader {
@@ -40,7 +41,7 @@ interface DemoPreviewStateWriter extends DemoPreviewStateReader {
40
41
  play(): void;
41
42
  renameTrack(trackId: string, newName: string): boolean;
42
43
  resetPreviewAspectRatioToAuto(): void;
43
- selectClip(clipId: string): void;
44
+ setSelection(clipIds: string[]): void;
44
45
  setCurrentTime(time: number): void;
45
46
  setPreviewAspectRatio(aspectRatio: {
46
47
  width: number;
@@ -1,5 +1,6 @@
1
- import type { TimelinePreviewBackend } from './previewBackend';
1
+ import type { TimelinePreviewBackend, TimelinePreviewBackendOptions } from './previewBackend';
2
2
  import { TimelinePreviewSession } from './timelinePreviewSession';
3
3
  export declare class DomPreviewBackend extends TimelinePreviewSession implements TimelinePreviewBackend {
4
+ constructor(options?: TimelinePreviewBackendOptions);
4
5
  destroy(): void;
5
6
  }
@@ -8,6 +8,8 @@ export * from './timelineDurationController';
8
8
  export { TimelineEventDispatcher } from './timelineEventDispatcher';
9
9
  export { TimelinePlaybackResolver } from './timelinePlaybackResolver';
10
10
  export { TimelinePreviewSession } from './timelinePreviewSession';
11
+ export { TimelinePreviewRuntimeController } from './timelinePreviewRuntimeController';
12
+ export { TimelinePreviewStateController, type TimelinePendingPreviewState } from './timelinePreviewStateController';
11
13
  export * from './timelineSelectionController';
12
14
  export * from './timelineTrackMutationController';
13
15
  export { TimelineTrackInfoPanelController } from './timelineTrackInfoPanelController';
@@ -7,13 +7,14 @@ export interface TimelinePreviewSyncPayload {
7
7
  currentTime: TimeMs;
8
8
  playState: PlayState;
9
9
  speed: number;
10
- selectedClipId?: string | null;
10
+ primarySelectedClipId?: string | null;
11
11
  syncRequestId?: number;
12
12
  }
13
13
  export interface TimelinePreviewBackendCallbacks {
14
14
  onBufferingStateChange?: (isBuffering: boolean) => void;
15
15
  onSourceLoadingChange?: (pending: number) => void;
16
16
  onSyncProcessed?: (syncRequestId?: number) => void;
17
+ onRuntimeStateChange?: (state: PreviewRuntimeState) => void;
17
18
  onAspectRatioChange?: (aspectRatio: PreviewAspectRatio) => void;
18
19
  onVisualTransformCommit?: (clipId: string, visualTransform: ClipVisualTransform) => void;
19
20
  onPendingPreviewRetry?: () => void;
@@ -26,6 +27,27 @@ export interface PreviewPendingState {
26
27
  isBuffering: boolean;
27
28
  errorMessage: string | null;
28
29
  }
30
+ export type PreviewRuntimePhase = 'idle' | 'awaiting-sync' | 'awaiting-media' | 'ready' | 'failed';
31
+ export type PreviewSlotPhase = 'idle' | 'binding' | 'primed' | 'active' | 'recovering' | 'failed';
32
+ export interface PreviewSlotDiagnostic {
33
+ trackId: string;
34
+ role: 'current' | 'preload';
35
+ kind: ActiveClipPlaybackInfo['trackType'];
36
+ clipId: string | null;
37
+ desiredSource: string | null;
38
+ actualSource: string | null;
39
+ phase: PreviewSlotPhase;
40
+ retryCount: number;
41
+ errorMessage: string | null;
42
+ }
43
+ export interface PreviewRuntimeState {
44
+ syncRequestId?: number;
45
+ phase: PreviewRuntimePhase;
46
+ loadingCount: number;
47
+ isBuffering: boolean;
48
+ errorMessage: string | null;
49
+ slots: PreviewSlotDiagnostic[];
50
+ }
29
51
  export interface TimelinePreviewBackend {
30
52
  attach(container: HTMLElement): void;
31
53
  detach(): void;
@@ -2,7 +2,7 @@ import { Theme, Clip } from '../models';
2
2
  export interface TimelineClipConfigControllerConfig {
3
3
  container: HTMLElement;
4
4
  theme: Theme;
5
- getSelectedClip: () => Clip | null;
5
+ getPrimarySelectedClip: () => Clip | null;
6
6
  updateClip: (clipId: string, updates: Partial<Clip>) => void;
7
7
  }
8
8
  export declare class TimelineClipConfigController {
@@ -0,0 +1,40 @@
1
+ import type { PreviewAspectRatio, PreviewBackendType } from '../models/types';
2
+ import type { TimelinePreviewBackend, TimelinePreviewBackendCallbacks } from './previewBackend';
3
+ interface TimelinePreviewRuntimeControllerOptions {
4
+ createBackendCallbacks: (callbackToken: number) => TimelinePreviewBackendCallbacks;
5
+ createBackend: (resolvedBackend: Exclude<PreviewBackendType, 'auto'>, callbacks: TimelinePreviewBackendCallbacks) => TimelinePreviewBackend;
6
+ resolveConfiguredBackend: (runtimeOverride: Exclude<PreviewBackendType, 'auto'> | null) => Exclude<PreviewBackendType, 'auto'>;
7
+ }
8
+ export declare class TimelinePreviewRuntimeController {
9
+ private readonly options;
10
+ private _previewSession?;
11
+ private _previewMountContainer;
12
+ private _resolvedPreviewBackend;
13
+ private _runtimePreviewBackendOverride;
14
+ private _activePreviewCallbackToken;
15
+ constructor(options: TimelinePreviewRuntimeControllerOptions);
16
+ get previewSession(): TimelinePreviewBackend | undefined;
17
+ set previewSession(value: TimelinePreviewBackend | undefined);
18
+ get previewMountContainer(): HTMLElement | null;
19
+ set previewMountContainer(value: HTMLElement | null);
20
+ get resolvedPreviewBackend(): Exclude<PreviewBackendType, 'auto'>;
21
+ set resolvedPreviewBackend(value: Exclude<PreviewBackendType, 'auto'>);
22
+ get runtimePreviewBackendOverride(): Exclude<PreviewBackendType, 'auto'> | null;
23
+ set runtimePreviewBackendOverride(value: Exclude<PreviewBackendType, 'auto'> | null);
24
+ get activePreviewCallbackToken(): number;
25
+ set activePreviewCallbackToken(value: number);
26
+ isActiveCallbackToken(callbackToken: number): boolean;
27
+ resolveConfiguredPreviewBackend(): Exclude<PreviewBackendType, 'auto'>;
28
+ createPreviewBackend(resolvedBackend?: Exclude<PreviewBackendType, 'auto'>): TimelinePreviewBackend;
29
+ getPreviewSession(): TimelinePreviewBackend;
30
+ hasAttachedPreview(): boolean;
31
+ attachPreview(container: HTMLElement, aspectRatio: PreviewAspectRatio): PreviewAspectRatio;
32
+ detachPreviewSession(): boolean;
33
+ destroyPreviewSession(clearReference?: boolean): void;
34
+ recreateDetachedPreviewSession(): void;
35
+ fallbackToDom(aspectRatio: PreviewAspectRatio): {
36
+ handled: boolean;
37
+ aspectRatio: PreviewAspectRatio;
38
+ };
39
+ }
40
+ export {};