@linker-design-plus/timeline-track 1.0.9 → 1.0.11

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 (75) hide show
  1. package/README.md +328 -372
  2. package/dist/components/interaction/globalPointerDragSession.d.ts +8 -0
  3. package/dist/components/panel/ClipConfigPanel.d.ts +48 -0
  4. package/dist/components/panel/TrackInfoPanel.d.ts +33 -0
  5. package/dist/components/scrollbar/KonvaScrollbarView.d.ts +58 -0
  6. package/dist/components/timeline/ManagedPlayhead.d.ts +29 -0
  7. package/dist/components/{Playhead.d.ts → timeline/Playhead.d.ts} +1 -1
  8. package/dist/components/{Timeline.d.ts → timeline/Timeline.d.ts} +27 -6
  9. package/dist/components/timeline/TimelineHeaderView.d.ts +45 -0
  10. package/dist/components/track/Clip.d.ts +16 -0
  11. package/dist/components/track/Track.d.ts +121 -0
  12. package/dist/components/track/trackClipLayout.d.ts +23 -0
  13. package/dist/components/track/trackInteractionState.d.ts +79 -0
  14. package/dist/core/commands/timelineCommands.d.ts +121 -0
  15. package/dist/core/controllers/demoPreviewRebuildState.d.ts +54 -0
  16. package/dist/core/controllers/domPreviewBackend.d.ts +5 -0
  17. package/dist/core/controllers/index.d.ts +13 -0
  18. package/dist/core/controllers/previewBackend.d.ts +53 -0
  19. package/dist/core/controllers/previewBackendSupport.d.ts +2 -0
  20. package/dist/core/controllers/previewClockController.d.ts +13 -0
  21. package/dist/core/controllers/previewTransformMath.d.ts +27 -0
  22. package/dist/core/controllers/previewTransformOverlay.d.ts +45 -0
  23. package/dist/core/controllers/timelineClipConfigController.d.ts +15 -0
  24. package/dist/core/controllers/timelineClipEventController.d.ts +32 -0
  25. package/dist/core/controllers/timelineClipWorkflowController.d.ts +30 -0
  26. package/dist/core/controllers/timelineDurationController.d.ts +14 -0
  27. package/dist/core/controllers/timelineEventDispatcher.d.ts +12 -0
  28. package/dist/core/controllers/timelinePlaybackResolver.d.ts +17 -0
  29. package/dist/core/controllers/timelinePreviewSession.d.ts +94 -0
  30. package/dist/core/controllers/timelineSelectionController.d.ts +17 -0
  31. package/dist/core/controllers/timelineTrackInfoPanelController.d.ts +19 -0
  32. package/dist/core/controllers/timelineTrackMutationController.d.ts +46 -0
  33. package/dist/core/facade/timelineManager.d.ts +382 -0
  34. package/dist/core/{history.d.ts → history/history.d.ts} +16 -14
  35. package/dist/core/history/index.d.ts +3 -0
  36. package/dist/core/history/timelineHistoryExecutor.d.ts +23 -0
  37. package/dist/core/history/timelineHistoryRecorder.d.ts +15 -0
  38. package/dist/core/layout/index.d.ts +1 -0
  39. package/dist/core/layout/timelineManagerDom.d.ts +22 -0
  40. package/dist/core/layout/timelineTrackLayout.d.ts +10 -0
  41. package/dist/core/models/clipState.d.ts +3 -0
  42. package/dist/core/{constants.d.ts → models/constants.d.ts} +4 -0
  43. package/dist/core/models/index.d.ts +3 -0
  44. package/dist/core/models/types.d.ts +392 -0
  45. package/dist/core/presentation/index.d.ts +1 -0
  46. package/dist/core/presentation/timelinePresentationAdapter.d.ts +22 -0
  47. package/dist/core/stores/index.d.ts +4 -0
  48. package/dist/core/stores/playbackStore.d.ts +17 -0
  49. package/dist/core/stores/selectionStore.d.ts +7 -0
  50. package/dist/core/stores/timelineStore.d.ts +44 -0
  51. package/dist/core/stores/viewportStore.d.ts +33 -0
  52. package/dist/core/testing/konva-test-stub.d.ts +81 -0
  53. package/dist/core/tracks/index.d.ts +3 -0
  54. package/dist/core/tracks/timelineTrackBridge.d.ts +37 -0
  55. package/dist/core/tracks/timelineTrackCollection.d.ts +50 -0
  56. package/dist/core/tracks/trackManager.d.ts +19 -0
  57. package/dist/core/utils/mountManager.d.ts +10 -0
  58. package/dist/index.cjs.js +29 -3
  59. package/dist/index.d.ts +12 -7
  60. package/dist/index.es.js +8914 -4353
  61. package/dist/utils/logging/Logger.d.ts +30 -0
  62. package/dist/utils/logging/index.d.ts +1 -0
  63. package/dist/utils/{KonvaUtils.d.ts → rendering/KonvaUtils.d.ts} +3 -43
  64. package/dist/utils/rendering/clipCoverRenderer.d.ts +23 -0
  65. package/dist/utils/rendering/clipVisualRenderer.d.ts +5 -0
  66. package/dist/utils/rendering/index.d.ts +4 -0
  67. package/dist/utils/rendering/timelineGridDrawing.d.ts +8 -0
  68. package/dist/utils/time/index.d.ts +1 -0
  69. package/dist/utils/{timeUtils.d.ts → time/timeUtils.d.ts} +4 -0
  70. package/package.json +5 -3
  71. package/dist/components/Clip.d.ts +0 -44
  72. package/dist/components/VideoTrack.d.ts +0 -126
  73. package/dist/core/timelineManager.d.ts +0 -212
  74. package/dist/core/types.d.ts +0 -183
  75. package/dist/utils/Logger.d.ts +0 -49
package/README.md CHANGED
@@ -1,26 +1,24 @@
1
1
  # @linker-design-plus/timeline-track
2
2
 
3
- 基于 TypeScript 和 Konva.js 的高性能视频编辑时间线库,支持拖拽、缩放、分割等核心功能,集成了完整的操作历史记录和日志系统。
4
-
5
- ## 核心功能
6
-
7
- - 交互式时间线,支持水平缩放(鼠标滚轮/触摸手势)
8
- - 视频轨道系统,支持多个片段的拖拽、调整大小、分割功能
9
- - ✅ 播放头指针,用于精确时间指示
10
- - 操作历史记录,支持撤销/重做功能
11
- - ✅ Canvas-based 渲染,确保高性能
12
- - ✅ 外部 API,用于片段管理、时间同步和播放控制
13
- - ✅ 片段碰撞检测,防止重叠
14
- - ✅ 片段边界限制,确保在轨道范围内
15
- - ✅ 原视频长度边界限制,确保剪辑不超出原视频范围
16
- - 日志系统,支持调试模式开关
17
- - ✅ 间隙移除功能,自动删除片段之间的间隙
18
- - 批量片段更新,提高性能
19
- - ✅ 历史记录变更通知事件,用于外部应用调整撤销/重做按钮状态
20
- - ✅ 播放倍速控制,支持 0.1x 到 10x 的播放速度
21
- - 轨道总时长计算,包含片段间隙
22
- - ✅ 封面系统,支持自定义缩略图提供器
23
- - ✅ 异步封面加载,支持 Promise 形式的封面获取
3
+ 基于 TypeScript 和 Konva 的时间线编辑组件库,提供单一主入口 `TimelineManager`,支持多轨片段编辑、缩放、分割、撤销重做、封面渲染以及多轨音视频预览播放。
4
+
5
+ ## 当前状态
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)
11
+
12
+ ## 能力概览
13
+
14
+ - 多轨时间线编辑,支持视频轨和音频轨
15
+ - 片段拖拽、拉伸、分割、跨轨移动、自动避让重叠
16
+ - 单一选中态,避免多个 clip 同时处于最终选中状态
17
+ - 播放头、滚动、缩放和时间同步
18
+ - 撤销 / 重做 / 历史变更通知
19
+ - 缩略图提供器与异步封面加载
20
+ - 多轨视频层级叠加预览与多轨音频混合播放
21
+ - 预览容器挂载、buffering 状态与预览比例同步
24
22
 
25
23
  ## 安装
26
24
 
@@ -30,402 +28,360 @@ npm install @linker-design-plus/timeline-track
30
28
 
31
29
  ## 快速开始
32
30
 
33
- ### 基本用法
34
-
35
- ```typescript
31
+ ```ts
36
32
  import { TimelineManager } from '@linker-design-plus/timeline-track';
37
33
 
38
- // 获取容器元素
39
- const timelineContainer = document.getElementById('timeline-container');
40
- const videoElement = document.getElementById('video-element') as HTMLVideoElement;
34
+ const container = document.getElementById('timeline-container') as HTMLDivElement;
35
+ const preview = document.getElementById('preview-container') as HTMLDivElement;
36
+
37
+ const timeline = new TimelineManager({
38
+ container,
39
+ debug: true,
40
+ zoom: 100,
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
+ }
41
59
 
42
- // 创建 TimelineManager 实例
43
- const timelineManager = new TimelineManager({
44
- container: timelineContainer,
45
- debug: true, // 开启调试模式
60
+ return null;
61
+ }
46
62
  });
47
63
 
48
- // 连接到视频元素(可选)
49
- timelineManager.connectTo(videoElement);
64
+ timeline.attachPreview(preview);
50
65
 
51
- // 添加片段
52
- const clipId = await timelineManager.addClip({
66
+ await timeline.addClip({
53
67
  src: 'sample-video.mp4',
54
68
  name: 'Clip 1',
55
- startTimeAtSource: 0, // 源视频中的开始时间(毫秒)
56
- duration: 5000, // 片段持续时间(毫秒)
57
- thumbnail: 'https://example.com/thumbnail1.jpg' // 可选:直接提供封面图片
69
+ startTimeAtSource: 0,
70
+ duration: 5000,
71
+ visualTransform: {
72
+ x: 0.5,
73
+ y: 0.5,
74
+ scale: 1
75
+ },
76
+ thumbnails: ['https://example.com/thumb-1.jpg']
58
77
  });
59
78
 
60
- // 开始播放
61
- timelineManager.play();
62
-
63
- // 设置播放倍速
64
- timelineManager.setSpeed(2); // 2 倍速
65
-
66
- // 适合缩放(自动调整缩放比例以适应所有片段)
67
- timelineManager.fitZoom();
68
-
69
- // 分割当前时间点的片段
70
- timelineManager.splitCurrentClip();
71
-
72
- // 移除片段之间的间隙
73
- timelineManager.removeClipGaps();
74
-
75
- // 监听历史记录变更事件
76
- timelineManager.on('history_change', (event, data) => {
77
- console.log('History changed:', data);
78
- // 更新撤销/重做按钮状态
79
- updateUndoRedoButtons(data.canUndo, data.canRedo);
79
+ timeline.on('history_change', (_event, data) => {
80
+ console.log('canUndo', data.canUndo, 'canRedo', data.canRedo);
80
81
  });
81
82
 
82
- // 销毁时间轴管理器
83
- // timelineManager.destroy();
83
+ timeline.play();
84
84
  ```
85
85
 
86
- ### 封面系统用法
86
+ ## 缩略图提供器
87
+
88
+ `ThumbnailProvider` 使用 `getThumbnails(clip)`,返回 `string[]` 或 `Promise<string[]>`。
89
+ 音频 clip 默认不会使用缩略图渲染,`thumbnailProvider` 主要作用于视频 clip。
87
90
 
88
- ```typescript
89
- import { TimelineManager, ThumbnailProvider } from '@linker-design-plus/timeline-track';
91
+ ```ts
92
+ import { TimelineManager, type ThumbnailProvider } from '@linker-design-plus/timeline-track';
90
93
 
91
- // 创建缩略图提供器
92
94
  const thumbnailProvider: ThumbnailProvider = {
93
- getThumbnail(clip) {
94
- // 同步获取封面
95
- return `https://example.com/thumbnails/${clip.id}.jpg`;
96
-
97
- // 或异步获取封面
98
- // return new Promise((resolve) => {
99
- // // 模拟异步获取封面
100
- // setTimeout(() => {
101
- // resolve(`https://example.com/thumbnails/${clip.id}.jpg`);
102
- // }, 100);
103
- // });
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
+ ];
104
100
  }
105
101
  };
106
102
 
107
- // 创建 TimelineManager 实例时设置缩略图提供器
108
- const timelineManager = new TimelineManager({
109
- container: timelineContainer,
110
- thumbnailProvider: thumbnailProvider
103
+ const timeline = new TimelineManager({
104
+ container,
105
+ thumbnailProvider
111
106
  });
112
107
 
113
- // 或动态设置缩略图提供器
114
- timelineManager.setThumbnailProvider(thumbnailProvider);
115
-
116
- // 添加片段时,会自动通过提供器获取封面
117
- const clipId = await timelineManager.addClip({
118
- src: 'sample-video.mp4',
119
- name: 'Clip 1',
120
- duration: 5000
121
- });
108
+ timeline.setThumbnailProvider(thumbnailProvider);
109
+ await timeline.refreshAllClipThumbnails();
122
110
  ```
123
111
 
124
- ### Vue 3 集成
125
-
126
- ```vue
127
- <template>
128
- <div class="app">
129
- <div class="video-container">
130
- <video ref="videoElement" class="video" controls playsinline></video>
131
- </div>
132
-
133
- <div class="controls">
134
- <button @click="togglePlay">{{ isPlaying ? 'Pause' : 'Play' }}</button>
135
- <button @click="fitZoom">适合缩放</button>
136
- <button @click="removeClipGaps">移除间隙</button>
137
- <button @click="undo" :disabled="!canUndo">Undo</button>
138
- <button @click="redo" :disabled="!canRedo">Redo</button>
139
- <button @click="addClip">Add Clip</button>
140
- <button @click="splitClip">Split Clip</button>
141
- </div>
142
-
143
- <div class="timeline-container" ref="timelineContainer"></div>
144
-
145
- <div class="info-panel">
146
- <div>Current Time: {{ formattedTime }}</div>
147
- <div>Zoom: {{ zoom }} px/s</div>
148
- <div>Clips: {{ clipCount }}</div>
149
- <div>Status: {{ isPlaying ? 'Playing' : 'Paused' }}</div>
150
- <div>Speed: {{ speed }}x</div>
151
- </div>
152
- </div>
153
- </template>
154
-
155
- <script setup lang="ts">
156
- import { ref, onMounted, onUnmounted, computed } from 'vue';
157
- import { TimelineManager } from '@linker-design-plus/timeline-track';
158
-
159
- // 容器引用
160
- const timelineContainer = ref<HTMLDivElement | null>(null);
161
- const videoElement = ref<HTMLVideoElement | null>(null);
162
-
163
- // 状态
164
- const isPlaying = ref(false);
165
- const currentTime = ref(0);
166
- const zoom = ref(100);
167
- const clipCount = ref(0);
168
- const canUndo = ref(false);
169
- const canRedo = ref(false);
170
- const speed = ref(1.0);
171
-
172
- // TimelineManager 实例
173
- let timelineManager: TimelineManager | null = null;
174
-
175
- // 格式化时间显示
176
- const formattedTime = computed(() => {
177
- const totalSeconds = Math.floor(currentTime.value / 1000);
178
- const hours = Math.floor(totalSeconds / 3600);
179
- const minutes = Math.floor((totalSeconds % 3600) / 60);
180
- const seconds = totalSeconds % 60;
181
- return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
182
- });
183
-
184
- // 初始化
185
- onMounted(async () => {
186
- if (!timelineContainer.value) return;
187
-
188
- // 创建 TimelineManager 实例
189
- timelineManager = new TimelineManager({
190
- container: timelineContainer.value,
191
- debug: true,
192
- });
193
-
194
- // 连接到视频元素
195
- if (videoElement.value) {
196
- timelineManager.connectTo(videoElement.value);
197
- }
198
-
199
- // 添加事件监听器
200
- timelineManager.on('time_change', (event, data) => {
201
- currentTime.value = data.time;
202
- });
203
-
204
- timelineManager.on('play_state_change', (event, data) => {
205
- isPlaying.value = data.playState === 'playing';
206
- });
207
-
208
- timelineManager.on('zoom_change', (event, data) => {
209
- zoom.value = data.zoom;
210
- });
211
-
212
- timelineManager.on('history_change', (event, data) => {
213
- canUndo.value = data.canUndo;
214
- canRedo.value = data.canRedo;
215
- });
216
-
217
- timelineManager.on('speed_change', (event, data) => {
218
- speed.value = data.speed;
219
- });
220
-
221
- // 添加示例片段
222
- await addSampleClips();
223
- updateClipCount();
224
-
225
- // 初始设置
226
- zoom.value = timelineManager.getZoom();
227
- speed.value = timelineManager.getSpeed();
228
- });
229
-
230
- // 清理
231
- onUnmounted(() => {
232
- timelineManager?.destroy();
233
- });
234
-
235
- // 控制方法
236
- const togglePlay = () => timelineManager?.togglePlay();
237
- const undo = () => timelineManager?.undo();
238
- const redo = () => timelineManager?.redo();
239
- const fitZoom = () => timelineManager?.fitZoom();
240
- const removeClipGaps = () => timelineManager?.removeClipGaps();
241
-
242
- // 添加片段
243
- const addClip = async () => {
244
- if (!timelineManager) return;
245
-
246
- const clipId = await timelineManager.addClip({
247
- src: 'sample-video.mp4',
248
- name: `Clip ${Date.now()}`,
249
- duration: 5000,
250
- startTimeAtSource: 0
251
- });
252
- updateClipCount();
253
- };
254
-
255
- // 分割片段
256
- const splitClip = () => timelineManager?.splitCurrentClip();
257
-
258
- // 更新片段计数
259
- const updateClipCount = () => {
260
- if (timelineManager) {
261
- clipCount.value = timelineManager.getClips().length;
262
- }
263
- };
112
+ ## 对外主 API
113
+
114
+ ### `TimelineManager`
115
+
116
+ 常用方法:
117
+
118
+ - `play()` / `pause()` / `togglePlay()`
119
+ - `setCurrentTime(time)` / `getCurrentTime()`
120
+ - `setZoom(zoom)` / `getZoom()`
121
+ - `setSpeed(speed)` / `getSpeed()`
122
+ - `addClip(config)` / `addClips(configs)`
123
+ - `updateClip(clipId, updates)` / `removeClip(clipId)`
124
+ - `selectClip(clipId)` / `clearSelection()` / `getSelectedClip()`
125
+ - `canSeparateClipAudio(clipId)` / `separateClipAudio(clipId)` / `canRestoreClipAudio(clipId)` / `restoreClipAudio(clipId)`
126
+ - `splitCurrentClip()` / `removeClipGaps()` / `fitZoom()`
127
+ - `undo()` / `redo()` / `clearHistory()`
128
+ - `attachPreview(containerOrConfig)` / `detachPreview()`
129
+ - `getPreviewBackendType()`
130
+ - `getCurrentActiveClips()` / `getActiveClipsAtTime(time)`
131
+ - `getPreviewAspectRatio()` / `setPreviewAspectRatio({ width, height })` / `resetPreviewAspectRatioToAuto()`
132
+ - `exportTimeline()`
133
+
134
+ 常用事件:
135
+
136
+ - `time_change`
137
+ - `play_state_change`
138
+ - `speed_change`
139
+ - `zoom_change`
140
+ - `preview_aspect_ratio_change`
141
+ - `clip_added`
142
+ - `clip_removed`
143
+ - `clip_updated`
144
+ - `clip_selected`
145
+ - `selected_clip_change`
146
+ - `history_change`
147
+ - `track_duration_change`
148
+ - `buffering_state_change`
149
+ - `can_play_change`
150
+ - `source_loading_change`
151
+
152
+ ## 核心类型
153
+
154
+ ```ts
155
+ interface ClipConfig {
156
+ id?: string;
157
+ type?: 'video' | 'audio';
158
+ externalId?: string;
159
+ src: string;
160
+ name: string;
161
+ isMuted?: boolean;
162
+ startTime?: number;
163
+ duration: number;
164
+ startTimeAtSource?: number;
165
+ endTimeAtSource?: number;
166
+ sourceDuration?: number;
167
+ thumbnails?: string[];
168
+ style?: Record<string, any>;
169
+ visualTransform?: {
170
+ x: number;
171
+ y: number;
172
+ scale: number;
173
+ };
174
+ separatedAudioClipId?: string;
175
+ separatedFromVideoClipId?: string;
176
+ }
177
+ ```
264
178
 
265
- // 添加示例片段
266
- const addSampleClips = async () => {
267
- if (!timelineManager) return;
268
-
269
- const clips = [
270
- {
271
- src: 'sample1.mp4',
272
- name: 'Clip 1',
273
- startTimeAtSource: 0,
274
- duration: 5000
275
- },
276
- {
277
- src: 'sample2.mp4',
278
- name: 'Clip 2',
279
- startTimeAtSource: 0,
280
- duration: 8000
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`,便于后端按当前多轨预览语义做合成。
183
+
184
+ ### 预览后端
185
+
186
+ `TimelineManager` 现在支持双预览后端:
187
+
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;
281
210
  }
282
- ];
283
211
 
284
- for (const clip of clips) {
285
- await timelineManager.addClip(clip);
212
+ return {
213
+ url: clip.src,
214
+ mimeType: 'video/mp4',
215
+ kind: 'mp4'
216
+ };
286
217
  }
287
-
288
- timelineManager.clearHistory();
289
- };
290
- </script>
291
-
292
- <style scoped>
293
- /* 样式省略 */
294
- </style>
218
+ });
295
219
  ```
296
220
 
297
- ## API 文档
298
-
299
- ### TimelineManager
300
-
301
- #### 构造函数
302
-
303
- ```typescript
304
- new TimelineManager(config?: Partial<TimelineConfig>)
221
+ 当前阶段限制:
222
+
223
+ - `canvas` 后端仅面向 Chrome / Edge 桌面端
224
+ - 视频预览源支持 MP4,以及 `EXT-X-MAP + m4s` 形式的 fMP4 HLS
225
+ - 暂不支持基于 TS 分片的 HLS,也不支持 DRM 流
226
+ - Firefox / Safari 会自动回退到 `dom` 后端
227
+
228
+ ## 当前代码架构
229
+
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
+ ### 目录结构
272
+
273
+ ```text
274
+ src/
275
+ components/ Konva 组件和交互 helper
276
+ core/ 状态、命令、controller、façade、类型
277
+ utils/ 渲染与通用工具
278
+ styles/ 样式入口
279
+ index.ts 对外导出
280
+ docs/
281
+ interaction-model.md
282
+ refactor-roadmap.md
283
+ demo/
284
+ App.vue
285
+ main.ts
305
286
  ```
306
287
 
307
- **参数:**
308
- - `config`:配置选项
309
- - `container`:容器元素
310
- - `debug`:是否开启调试模式,默认 false
311
- - `duration`:总时长(毫秒),默认 3600000
312
- - `zoom`:缩放比例(像素/秒),默认 100
313
- - `currentTime`:初始当前时间(毫秒),默认 0
314
- - `playState`:初始播放状态,默认 'paused'
315
- - `speed`:初始播放倍速,默认 1.0
316
- - `thumbnailProvider`:缩略图提供器,用于获取片段封面
317
-
318
- #### 核心方法
319
-
320
- | 方法名 | 描述 | 参数 | 返回值 |
321
- |--------|------|------|--------|
322
- | `play()` | 开始播放 | | |
323
- | `pause()` | 暂停播放 | 无 | 无 |
324
- | `togglePlay()` | 切换播放状态 | 无 | 无 |
325
- | `setCurrentTime(time)` | 设置当前时间 | `time`:时间(毫秒) | 无 |
326
- | `getCurrentTime()` | 获取当前时间 | 无 | `number` |
327
- | `setZoom(zoom)` | 设置缩放比例 | `zoom`:缩放比例(像素/秒) | 无 |
328
- | `getZoom()` | 获取缩放比例 | 无 | `number` |
329
- | `setSpeed(speed)` | 设置播放倍速 | `speed`:播放倍速 | 无 |
330
- | `getSpeed()` | 获取当前播放倍速 | 无 | `number` |
331
- | `setThumbnailProvider(provider)` | 设置缩略图提供器 | `provider`:缩略图提供器 | 无 |
332
- | `addClip(clipConfig)` | 添加片段 | `clipConfig`:片段配置 | `Promise<string>`(片段 ID) |
333
- | `removeClip(clipId)` | 移除片段 | `clipId`:片段 ID | 无 |
334
- | `removeSelectedClip()` | 移除当前选中的片段 | 无 | `boolean`(是否成功) |
335
- | `splitCurrentClip()` | 分割当前时间点的片段 | 无 | 无 |
336
- | `removeClipGaps()` | 移除片段之间的间隙 | 无 | 无 |
337
- | `fitZoom()` | 自动调整缩放比例以适应所有片段 | 无 | 无 |
338
- | `getClips()` | 获取所有片段 | 无 | `Clip[]` |
339
- | `getCurrentClip()` | 获取当前时间点所在的片段 | 无 | `Clip` 或 `null` |
340
- | `getSelectedClip()` | 获取当前选中的片段 | 无 | `Clip` 或 `null` |
341
- | `getTrackTotalDuration()` | 获取轨道总时长(包含间隙) | 无 | `number` |
342
- | `exportTimeline()` | 导出时间轴数据 | 无 | `TimelineExportData` |
343
- | `undo()` | 撤销操作 | 无 | `boolean`(是否成功) |
344
- | `redo()` | 重做操作 | 无 | `boolean`(是否成功) |
345
- | `clearHistory()` | 清空历史记录 | 无 | 无 |
346
- | `connectTo(video)` | 连接到视频元素 | `video`:视频元素 | 无 |
347
- | `on(event, listener)` | 添加事件监听器 | `event`:事件类型,`listener`:事件监听器 | 无 |
348
- | `off(event, listener)` | 移除事件监听器 | `event`:事件类型,`listener`:事件监听器 | 无 |
349
- | `destroy()` | 销毁时间轴管理器 | 无 | 无 |
350
-
351
- #### 事件
352
-
353
- | 事件名 | 描述 | 数据 |
354
- |--------|------|------|
355
- | `time_change` | 当前时间变化 | `{ time: number }` |
356
- | `play_state_change` | 播放状态变化 | `{ playState: 'playing' \| 'paused' }` |
357
- | `speed_change` | 播放倍速变化 | `{ speed: number }` |
358
- | `clip_added` | 添加片段 | `{ clip: Clip }` |
359
- | `clip_removed` | 移除片段 | `{ clipId: string }` |
360
- | `clip_updated` | 更新片段 | `{ clip: Clip }` |
361
- | `clip_selected` | 选择片段 | `{ clip: Clip }` |
362
- | `zoom_change` | 缩放比例变化 | `{ zoom: number }` |
363
- | `history_change` | 历史记录变更 | `{ canUndo: boolean, canRedo: boolean }` |
364
- | `track_duration_change` | 轨道总时长变化 | `{ duration: number }` |
365
- | `buffering_state_change` | 视频缓冲状态变化 | `{ isBuffering: boolean }` |
366
-
367
- ### ClipConfig 接口
368
-
369
- ```typescript
370
- interface ClipConfig {
371
- src: string; // 视频源 URL
372
- name?: string; // 片段名称
373
- duration: number; // 片段持续时间(毫秒)
374
- startTimeAtSource?: number; // 源视频中的开始时间(毫秒)
375
- startTime?: number; // 片段在轨道上的起始时间(可选,自动计算)
376
- thumbnail?: string; // 缩略图 URL
377
- }
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
378
315
  ```
379
316
 
380
- ## 开发指南
381
-
382
- ### 安装依赖
383
-
384
- ```bash
385
- npm install
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对外事件通知"]
386
331
  ```
387
332
 
388
- ### 开发模式
389
-
390
- ```bash
391
- npm run dev
392
- ```
333
+ ### 核心分层说明
393
334
 
394
- ### 构建
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` 两套多轨预览实现。
395
346
 
396
- ```bash
397
- npm run build
398
- ```
347
+ ### 典型调用链路
399
348
 
400
- ### 预览构建结果
349
+ #### 1. Clip 拖拽/跨轨移动
401
350
 
402
- ```bash
403
- npm run preview
404
- ```
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,并触发重绘、事件通知与预览刷新。
405
356
 
406
- ## 浏览器兼容性
357
+ #### 2. 播放与时间推进
407
358
 
408
- - Chrome (推荐)
409
- - Firefox
410
- - Safari
411
- - Edge
359
+ 1. `TimelineManager` 持有播放状态与当前时间。
360
+ 2. `TimelinePlaybackResolver` 与预览 session 根据当前时间解析 active clips。
361
+ 3. `DomPreviewBackend` 或 `CanvasPreviewBackend` 执行多轨音视频预览。
362
+ 4. 时间变化通过 `TimelineEventDispatcher` 向外发布 `time_change`、`play_state_change` 等事件。
412
363
 
413
- ## 许可证
364
+ ### 当前架构特点
414
365
 
415
- MIT
366
+ - 对外仍保持单一 façade:调用方只需面向 `TimelineManager`。
367
+ - 内部已经拆成 store / commands / controllers / tracks / presentation,职责边界比早期单体 manager 更清晰。
368
+ - UI 交互和领域规则分离:交互细节主要留在 `components`,领域决策集中在 `core`。
369
+ - 预览后端可替换:预览能力通过 backend 抽象扩展,而不是直接耦合到时间轴视图。
370
+ - 历史系统贯穿工作流:新增、删除、更新、跨轨移动都能进入 undo / redo 闭环。
416
371
 
417
- ## 贡献
372
+ ## 开发
418
373
 
419
- 欢迎提交 Issue 和 Pull Request!
374
+ ```bash
375
+ pnpm install
376
+ pnpm run dev
377
+ pnpm run build
378
+ pnpm test
379
+ pnpm exec tsc -p tsconfig.json --noEmit
380
+ ```
420
381
 
421
- ## 更新日志
382
+ ## 浏览器支持
422
383
 
423
- ### v1.0.0-beta.1
424
- - 首次发布
425
- - 基于 TypeScript 和 Konva.js 构建
426
- - 实现核心视频编辑时间线功能
427
- - 支持片段拖拽、调整大小、分割
428
- - 集成操作历史记录系统
429
- - 支持播放倍速控制
430
- - 提供完整的外部 API
431
- - 包含 TypeScript 类型声明
384
+ - Chrome
385
+ - Edge
386
+ - Firefox(`dom` 后端)
387
+ - Safari(`dom` 后端)