@linker-design-plus/timeline-track 1.0.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 ADDED
@@ -0,0 +1,387 @@
1
+ # @linker-design-plus/timeline-track
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
+ ## 安装
24
+
25
+ ```bash
26
+ npm install @linker-design-plus/timeline-track
27
+ ```
28
+
29
+ ## 快速开始
30
+
31
+ ### 基本用法
32
+
33
+ ```typescript
34
+ import { TimelineManager } from '@linker-design-plus/timeline-track';
35
+
36
+ // 获取容器元素
37
+ const timelineContainer = document.getElementById('timeline-container');
38
+ const videoElement = document.getElementById('video-element') as HTMLVideoElement;
39
+
40
+ // 创建 TimelineManager 实例
41
+ const timelineManager = new TimelineManager({
42
+ container: timelineContainer,
43
+ debug: true, // 开启调试模式
44
+ });
45
+
46
+ // 连接到视频元素(可选)
47
+ timelineManager.connectTo(videoElement);
48
+
49
+ // 添加片段
50
+ const clipId = await timelineManager.addClip({
51
+ src: 'sample-video.mp4',
52
+ name: 'Clip 1',
53
+ startTimeAtSource: 0, // 源视频中的开始时间(毫秒)
54
+ duration: 5000 // 片段持续时间(毫秒)
55
+ });
56
+
57
+ // 开始播放
58
+ timelineManager.play();
59
+
60
+ // 设置播放倍速
61
+ timelineManager.setSpeed(2); // 2 倍速
62
+
63
+ // 适合缩放(自动调整缩放比例以适应所有片段)
64
+ timelineManager.fitZoom();
65
+
66
+ // 分割当前时间点的片段
67
+ timelineManager.splitCurrentClip();
68
+
69
+ // 移除片段之间的间隙
70
+ timelineManager.removeClipGaps();
71
+
72
+ // 监听历史记录变更事件
73
+ timelineManager.on('history_change', (event, data) => {
74
+ console.log('History changed:', data);
75
+ // 更新撤销/重做按钮状态
76
+ updateUndoRedoButtons(data.canUndo, data.canRedo);
77
+ });
78
+
79
+ // 销毁时间轴管理器
80
+ // timelineManager.destroy();
81
+ ```
82
+
83
+ ### Vue 3 集成
84
+
85
+ ```vue
86
+ <template>
87
+ <div class="app">
88
+ <div class="video-container">
89
+ <video ref="videoElement" class="video" controls playsinline></video>
90
+ </div>
91
+
92
+ <div class="controls">
93
+ <button @click="togglePlay">{{ isPlaying ? 'Pause' : 'Play' }}</button>
94
+ <button @click="fitZoom">适合缩放</button>
95
+ <button @click="removeClipGaps">移除间隙</button>
96
+ <button @click="undo" :disabled="!canUndo">Undo</button>
97
+ <button @click="redo" :disabled="!canRedo">Redo</button>
98
+ <button @click="addClip">Add Clip</button>
99
+ <button @click="splitClip">Split Clip</button>
100
+ </div>
101
+
102
+ <div class="timeline-container" ref="timelineContainer"></div>
103
+
104
+ <div class="info-panel">
105
+ <div>Current Time: {{ formattedTime }}</div>
106
+ <div>Zoom: {{ zoom }} px/s</div>
107
+ <div>Clips: {{ clipCount }}</div>
108
+ <div>Status: {{ isPlaying ? 'Playing' : 'Paused' }}</div>
109
+ <div>Speed: {{ speed }}x</div>
110
+ </div>
111
+ </div>
112
+ </template>
113
+
114
+ <script setup lang="ts">
115
+ import { ref, onMounted, onUnmounted, computed } from 'vue';
116
+ import { TimelineManager } from '@linker-design-plus/timeline-track';
117
+
118
+ // 容器引用
119
+ const timelineContainer = ref<HTMLDivElement | null>(null);
120
+ const videoElement = ref<HTMLVideoElement | null>(null);
121
+
122
+ // 状态
123
+ const isPlaying = ref(false);
124
+ const currentTime = ref(0);
125
+ const zoom = ref(100);
126
+ const clipCount = ref(0);
127
+ const canUndo = ref(false);
128
+ const canRedo = ref(false);
129
+ const speed = ref(1.0);
130
+
131
+ // TimelineManager 实例
132
+ let timelineManager: TimelineManager | null = null;
133
+
134
+ // 格式化时间显示
135
+ const formattedTime = computed(() => {
136
+ const totalSeconds = Math.floor(currentTime.value / 1000);
137
+ const hours = Math.floor(totalSeconds / 3600);
138
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
139
+ const seconds = totalSeconds % 60;
140
+ return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
141
+ });
142
+
143
+ // 初始化
144
+ onMounted(async () => {
145
+ if (!timelineContainer.value) return;
146
+
147
+ // 创建 TimelineManager 实例
148
+ timelineManager = new TimelineManager({
149
+ container: timelineContainer.value,
150
+ debug: true,
151
+ });
152
+
153
+ // 连接到视频元素
154
+ if (videoElement.value) {
155
+ timelineManager.connectTo(videoElement.value);
156
+ }
157
+
158
+ // 添加事件监听器
159
+ timelineManager.on('time_change', (event, data) => {
160
+ currentTime.value = data.time;
161
+ });
162
+
163
+ timelineManager.on('play_state_change', (event, data) => {
164
+ isPlaying.value = data.playState === 'playing';
165
+ });
166
+
167
+ timelineManager.on('zoom_change', (event, data) => {
168
+ zoom.value = data.zoom;
169
+ });
170
+
171
+ timelineManager.on('history_change', (event, data) => {
172
+ canUndo.value = data.canUndo;
173
+ canRedo.value = data.canRedo;
174
+ });
175
+
176
+ timelineManager.on('speed_change', (event, data) => {
177
+ speed.value = data.speed;
178
+ });
179
+
180
+ // 添加示例片段
181
+ await addSampleClips();
182
+ updateClipCount();
183
+
184
+ // 初始设置
185
+ zoom.value = timelineManager.getZoom();
186
+ speed.value = timelineManager.getSpeed();
187
+ });
188
+
189
+ // 清理
190
+ onUnmounted(() => {
191
+ timelineManager?.destroy();
192
+ });
193
+
194
+ // 控制方法
195
+ const togglePlay = () => timelineManager?.togglePlay();
196
+ const undo = () => timelineManager?.undo();
197
+ const redo = () => timelineManager?.redo();
198
+ const fitZoom = () => timelineManager?.fitZoom();
199
+ const removeClipGaps = () => timelineManager?.removeClipGaps();
200
+
201
+ // 添加片段
202
+ const addClip = async () => {
203
+ if (!timelineManager) return;
204
+
205
+ const clipId = await timelineManager.addClip({
206
+ src: 'sample-video.mp4',
207
+ name: `Clip ${Date.now()}`,
208
+ duration: 5000,
209
+ startTimeAtSource: 0
210
+ });
211
+ updateClipCount();
212
+ };
213
+
214
+ // 分割片段
215
+ const splitClip = () => timelineManager?.splitCurrentClip();
216
+
217
+ // 更新片段计数
218
+ const updateClipCount = () => {
219
+ if (timelineManager) {
220
+ clipCount.value = timelineManager.getClips().length;
221
+ }
222
+ };
223
+
224
+ // 添加示例片段
225
+ const addSampleClips = async () => {
226
+ if (!timelineManager) return;
227
+
228
+ const clips = [
229
+ {
230
+ src: 'sample1.mp4',
231
+ name: 'Clip 1',
232
+ startTimeAtSource: 0,
233
+ duration: 5000
234
+ },
235
+ {
236
+ src: 'sample2.mp4',
237
+ name: 'Clip 2',
238
+ startTimeAtSource: 0,
239
+ duration: 8000
240
+ }
241
+ ];
242
+
243
+ for (const clip of clips) {
244
+ await timelineManager.addClip(clip);
245
+ }
246
+
247
+ timelineManager.clearHistory();
248
+ };
249
+ </script>
250
+
251
+ <style scoped>
252
+ /* 样式省略 */
253
+ </style>
254
+ ```
255
+
256
+ ## API 文档
257
+
258
+ ### TimelineManager
259
+
260
+ #### 构造函数
261
+
262
+ ```typescript
263
+ new TimelineManager(config?: Partial<TimelineConfig>)
264
+ ```
265
+
266
+ **参数:**
267
+ - `config`:配置选项
268
+ - `container`:容器元素
269
+ - `debug`:是否开启调试模式,默认 false
270
+ - `duration`:总时长(毫秒),默认 3600000
271
+ - `zoom`:缩放比例(像素/秒),默认 100
272
+ - `currentTime`:初始当前时间(毫秒),默认 0
273
+ - `playState`:初始播放状态,默认 'paused'
274
+ - `speed`:初始播放倍速,默认 1.0
275
+
276
+ #### 核心方法
277
+
278
+ | 方法名 | 描述 | 参数 | 返回值 |
279
+ |--------|------|------|--------|
280
+ | `play()` | 开始播放 | 无 | 无 |
281
+ | `pause()` | 暂停播放 | 无 | 无 |
282
+ | `togglePlay()` | 切换播放状态 | 无 | 无 |
283
+ | `setCurrentTime(time)` | 设置当前时间 | `time`:时间(毫秒) | 无 |
284
+ | `getCurrentTime()` | 获取当前时间 | 无 | `number` |
285
+ | `setZoom(zoom)` | 设置缩放比例 | `zoom`:缩放比例(像素/秒) | 无 |
286
+ | `getZoom()` | 获取缩放比例 | 无 | `number` |
287
+ | `setSpeed(speed)` | 设置播放倍速 | `speed`:播放倍速 | 无 |
288
+ | `getSpeed()` | 获取播放倍速 | 无 | `number` |
289
+ | `addClip(clipConfig)` | 添加片段 | `clipConfig`:片段配置 | `Promise<string>`(片段 ID) |
290
+ | `removeClip(clipId)` | 移除片段 | `clipId`:片段 ID | 无 |
291
+ | `removeSelectedClip()` | 移除当前选中的片段 | 无 | `boolean`(是否成功) |
292
+ | `splitCurrentClip()` | 分割当前时间点的片段 | 无 | 无 |
293
+ | `removeClipGaps()` | 移除片段之间的间隙 | 无 | 无 |
294
+ | `fitZoom()` | 自动调整缩放比例以适应所有片段 | 无 | 无 |
295
+ | `getClips()` | 获取所有片段 | 无 | `Clip[]` |
296
+ | `getCurrentClip()` | 获取当前时间点所在的片段 | 无 | `Clip` 或 `null` |
297
+ | `getSelectedClip()` | 获取当前选中的片段 | 无 | `Clip` 或 `null` |
298
+ | `getTrackTotalDuration()` | 获取轨道总时长(包含间隙) | 无 | `number` |
299
+ | `exportTimeline()` | 导出时间轴数据 | 无 | `TimelineExportData` |
300
+ | `undo()` | 撤销操作 | 无 | `boolean`(是否成功) |
301
+ | `redo()` | 重做操作 | 无 | `boolean`(是否成功) |
302
+ | `clearHistory()` | 清空历史记录 | 无 | 无 |
303
+ | `connectTo(video)` | 连接到视频元素 | `video`:视频元素 | 无 |
304
+ | `on(event, listener)` | 添加事件监听器 | `event`:事件类型,`listener`:事件监听器 | 无 |
305
+ | `off(event, listener)` | 移除事件监听器 | `event`:事件类型,`listener`:事件监听器 | 无 |
306
+ | `destroy()` | 销毁时间轴管理器 | 无 | 无 |
307
+
308
+ #### 事件
309
+
310
+ | 事件名 | 描述 | 数据 |
311
+ |--------|------|------|
312
+ | `time_change` | 当前时间变化 | `{ time: number }` |
313
+ | `play_state_change` | 播放状态变化 | `{ playState: 'playing' \| 'paused' }` |
314
+ | `speed_change` | 播放倍速变化 | `{ speed: number }` |
315
+ | `clip_added` | 添加片段 | `{ clip: Clip }` |
316
+ | `clip_removed` | 移除片段 | `{ clipId: string }` |
317
+ | `clip_updated` | 更新片段 | `{ clip: Clip }` |
318
+ | `clip_selected` | 选择片段 | `{ clip: Clip }` |
319
+ | `zoom_change` | 缩放比例变化 | `{ zoom: number }` |
320
+ | `history_change` | 历史记录变更 | `{ canUndo: boolean, canRedo: boolean }` |
321
+ | `track_duration_change` | 轨道总时长变化 | `{ duration: number }` |
322
+
323
+ ### ClipConfig 接口
324
+
325
+ ```typescript
326
+ interface ClipConfig {
327
+ src: string; // 视频源 URL
328
+ name?: string; // 片段名称
329
+ duration: number; // 片段持续时间(毫秒)
330
+ startTimeAtSource?: number; // 源视频中的开始时间(毫秒)
331
+ startTime?: number; // 片段在轨道上的起始时间(可选,自动计算)
332
+ thumbnail?: string; // 缩略图 URL
333
+ }
334
+ ```
335
+
336
+ ## 开发指南
337
+
338
+ ### 安装依赖
339
+
340
+ ```bash
341
+ npm install
342
+ ```
343
+
344
+ ### 开发模式
345
+
346
+ ```bash
347
+ npm run dev
348
+ ```
349
+
350
+ ### 构建
351
+
352
+ ```bash
353
+ npm run build
354
+ ```
355
+
356
+ ### 预览构建结果
357
+
358
+ ```bash
359
+ npm run preview
360
+ ```
361
+
362
+ ## 浏览器兼容性
363
+
364
+ - Chrome (推荐)
365
+ - Firefox
366
+ - Safari
367
+ - Edge
368
+
369
+ ## 许可证
370
+
371
+ MIT
372
+
373
+ ## 贡献
374
+
375
+ 欢迎提交 Issue 和 Pull Request!
376
+
377
+ ## 更新日志
378
+
379
+ ### v1.0.0-beta.1
380
+ - 首次发布
381
+ - 基于 TypeScript 和 Konva.js 构建
382
+ - 实现核心视频编辑时间线功能
383
+ - 支持片段拖拽、调整大小、分割
384
+ - 集成操作历史记录系统
385
+ - 支持播放倍速控制
386
+ - 提供完整的外部 API
387
+ - 包含 TypeScript 类型声明
@@ -0,0 +1,57 @@
1
+ import { default as Konva } from 'konva';
2
+ import { Clip as ClipType, Position } from '../core/types';
3
+
4
+ export declare class Clip {
5
+ private clip;
6
+ private onUpdate;
7
+ private onSplit;
8
+ private isMoving;
9
+ private dragStartPos;
10
+ private originalStart;
11
+ private originalDuration;
12
+ private originalStartTimeAtSource;
13
+ private originalEndTimeAtSource;
14
+ private originalOpacity;
15
+ constructor(clip: ClipType, onUpdate: (clip: ClipType) => void, onSplit: (clip: ClipType, time: number) => void);
16
+ /**
17
+ * 检查与其他片段是否重叠
18
+ */
19
+ checkCollision(newStartTime: number, newDuration: number, otherClips: ClipType[]): ClipType | undefined;
20
+ /**
21
+ * 检查是否超出边界
22
+ */
23
+ checkBoundary(newStartTimeAtSource: number, newEndTimeAtSource: number, newDuration: number, newStartTime: number): boolean;
24
+ /**
25
+ * 获取片段的中心点
26
+ */
27
+ getCenterTime(): number;
28
+ getClip(): ClipType;
29
+ setSelected(selected: boolean): void;
30
+ isPointInClip(x: number, y: number, zoom: number, trackY: number, trackHeight: number, scrollLeft: number): boolean;
31
+ isPointInResizeHandle(x: number, y: number, zoom: number, trackY: number, trackHeight: number, scrollLeft: number): boolean;
32
+ startDrag(pos: Position): void;
33
+ startResize(pos: Position): void;
34
+ /**
35
+ * 拖动片段或调整片段大小
36
+ * @param pos 当前鼠标位置
37
+ * @param zoom 缩放比例
38
+ * @param otherClips 其他片段列表(用于碰撞检测)
39
+ * @param enableBoundaryCheck 是否启用边界检查
40
+ * @returns 是否成功更新(即是否在边界内)
41
+ */
42
+ drag(pos: Position, zoom: number, otherClips?: ClipType[], enableBoundaryCheck?: boolean): boolean;
43
+ endDrag(): void;
44
+ split(time: number): void;
45
+ /**
46
+ * 创建Konva片段组
47
+ */
48
+ createKonvaGroup(zoom: number, scrollLeft: number, _trackY: number, trackHeight: number): Konva.Group;
49
+ /**
50
+ * 渲染调整手柄
51
+ */
52
+ private renderResizeHandles;
53
+ /**
54
+ * 更新Konva片段组
55
+ */
56
+ updateKonvaGroup(group: Konva.Group, zoom: number, scrollLeft: number, trackHeight: number): void;
57
+ }
@@ -0,0 +1,28 @@
1
+ import { default as Konva } from 'konva';
2
+ import { TimeMs, Theme } from '../core/types';
3
+
4
+ export declare class Playhead {
5
+ private layer;
6
+ private playheadGroup;
7
+ private playheadLine;
8
+ private playheadTriangle;
9
+ private theme;
10
+ private currentTime;
11
+ private zoom;
12
+ private scrollLeft;
13
+ private onTimeChange;
14
+ private isDragging;
15
+ constructor(layer: Konva.Layer, initialTime: TimeMs, zoom: number, height: number, theme: Theme, onTimeChange: (time: TimeMs) => void);
16
+ private initEventListeners;
17
+ private handleMouseMove;
18
+ setTime(time: TimeMs): void;
19
+ setZoom(zoom: number): void;
20
+ setScrollLeft(scrollLeft: number): void;
21
+ setHeight(height: number): void;
22
+ render(): void;
23
+ getTime(): TimeMs;
24
+ /**
25
+ * 获取播放头组
26
+ */
27
+ getPlayheadGroup(): Konva.Group;
28
+ }
@@ -0,0 +1,48 @@
1
+ import { default as Konva } from 'konva';
2
+ import { TimelineConfig, TimeMs } from '../core/types';
3
+
4
+ export declare class Timeline {
5
+ private stage;
6
+ private gridLayer;
7
+ private config;
8
+ private theme;
9
+ private isDragging;
10
+ private startDragX;
11
+ private scrollLeft;
12
+ private timeScaleHeight;
13
+ private onTimeChange;
14
+ private onZoomChange;
15
+ private onScrollChange;
16
+ private animationFrameId;
17
+ private isZooming;
18
+ constructor(stage: Konva.Stage, gridLayer: Konva.Layer, config: Partial<TimelineConfig>, onTimeChange: (time: TimeMs) => void, onZoomChange: (zoom: number) => void, onScrollChange: (scrollLeft: number) => void);
19
+ private initEventListeners;
20
+ private handleZoom;
21
+ private animateZoom;
22
+ private easeOutCubic;
23
+ private handleClick;
24
+ private pixelToTime;
25
+ private timeToPixel;
26
+ setCurrentTime(time: TimeMs): void;
27
+ setZoom(zoom: number): void;
28
+ setDuration(duration: TimeMs): void;
29
+ setPlayState(playState: 'playing' | 'paused'): void;
30
+ update(): void;
31
+ private render;
32
+ private renderTimeTicks;
33
+ getConfig(): TimelineConfig;
34
+ getScrollLeft(): number;
35
+ setScrollLeft(scrollLeft: number): void;
36
+ /**
37
+ * 获取舞台实例
38
+ */
39
+ getStage(): Konva.Stage;
40
+ /**
41
+ * 获取网格图层
42
+ */
43
+ getGridLayer(): Konva.Layer;
44
+ /**
45
+ * 调整大小
46
+ */
47
+ resize(_width: number, _height: number): void;
48
+ }
@@ -0,0 +1,117 @@
1
+ import { default as Konva } from 'konva';
2
+ import { Clip as ClipType, TrackConfig, TimeMs, Theme } from '../core/types';
3
+
4
+ export declare class VideoTrack {
5
+ private layer;
6
+ private trackGroup;
7
+ private config;
8
+ private theme;
9
+ private clips;
10
+ private clipGroups;
11
+ private zoom;
12
+ private scrollLeft;
13
+ private trackY;
14
+ private trackHeight;
15
+ private selectedClip;
16
+ private originalClipsState;
17
+ private nonDraggedClips;
18
+ private isVisualUpdate;
19
+ private onClipUpdate;
20
+ private onClipAdd;
21
+ private onClipRemove;
22
+ private onClipSplit;
23
+ private onClipSelect;
24
+ constructor(layer: Konva.Layer, config: TrackConfig, zoom: number, trackY: number, trackHeight: number, theme: Theme, onClipUpdate: (clip: ClipType, originalClip?: ClipType, clipUpdates?: Array<{
25
+ clipId: string;
26
+ newState: any;
27
+ previousState: any;
28
+ }>) => void, onClipAdd: (clip: ClipType) => void, onClipRemove: (clipId: string) => void, onClipSplit: (clip1: ClipType, clip2: ClipType) => void, onClipSelect: (clip: ClipType) => void);
29
+ private initClips;
30
+ private initEventListeners;
31
+ /**
32
+ * 创建片段组
33
+ */
34
+ private createClipGroup;
35
+ /**
36
+ * 处理片段点击事件
37
+ */
38
+ private handleClipClick;
39
+ /**
40
+ * 处理片段鼠标按下事件
41
+ */
42
+ private handleClipMouseDown;
43
+ /**
44
+ * 处理片段鼠标移动事件
45
+ */
46
+ private handleClipMouseMove;
47
+ /**
48
+ * 处理片段鼠标释放事件
49
+ */
50
+ private handleClipMouseUp;
51
+ /**
52
+ * 处理片段移动结束后的重叠检测和重新排序
53
+ */
54
+ private handleClipMoveEnd;
55
+ /**
56
+ * 调整片段以解决重叠问题
57
+ * @param clips 需要调整的片段数组
58
+ * @param startIndex 开始调整的索引
59
+ */
60
+ private adjustClipsForOverlap;
61
+ /**
62
+ * 批量更新所有片段状态
63
+ * @param processedClips 处理后的片段数组
64
+ */
65
+ private updateAllClips;
66
+ /**
67
+ * 构建并发送更新数据
68
+ * @param draggedClip 拖动的片段
69
+ * @param processedClips 处理后的片段数组
70
+ */
71
+ private buildAndSendUpdates;
72
+ /**
73
+ * 处理舞台鼠标移动事件
74
+ */
75
+ private handleStageMouseMove;
76
+ /**
77
+ * 处理片段鼠标离开事件
78
+ */
79
+ private handleClipMouseLeave;
80
+ /**
81
+ * 更新片段的视觉状态
82
+ */
83
+ private updateClipVisualState;
84
+ /**
85
+ * 更新片段组
86
+ */
87
+ private updateClipGroup;
88
+ private handleClipUpdate;
89
+ private handleClipSplit;
90
+ addClip(clip: ClipType): void;
91
+ removeClip(clipId: string): void;
92
+ updateClip(clipId: string, updates: Partial<ClipType>): void;
93
+ setZoom(zoom: number): void;
94
+ setCurrentTime(_time: TimeMs): void;
95
+ setScrollLeft(scrollLeft: number): void;
96
+ /**
97
+ * 更新片段的可见性
98
+ */
99
+ private updateClipVisibility;
100
+ setTrackY(trackY: number): void;
101
+ render(): void;
102
+ getClips(): ClipType[];
103
+ getSelectedClip(): ClipType | null;
104
+ splitSelectedClip(time: TimeMs): void;
105
+ /**
106
+ * 删除轨道内片段之间的空隙
107
+ */
108
+ removeClipGaps(): void;
109
+ /**
110
+ * 获取轨道组
111
+ */
112
+ getTrackGroup(): Konva.Group;
113
+ /**
114
+ * 获取轨道ID
115
+ */
116
+ getId(): string;
117
+ }
@@ -0,0 +1,54 @@
1
+ import { Clip, ClipConfig } from './types';
2
+
3
+ export declare class ClipManager {
4
+ private clips;
5
+ private onClipChange;
6
+ constructor(onClipChange: (clips: Clip[]) => void);
7
+ /**
8
+ * 添加片段
9
+ */
10
+ addClip(config: ClipConfig): string;
11
+ /**
12
+ * 获取片段
13
+ */
14
+ getClip(id: string): Clip | undefined;
15
+ /**
16
+ * 获取所有片段
17
+ */
18
+ getAllClips(): Clip[];
19
+ /**
20
+ * 更新片段
21
+ */
22
+ updateClip(id: string, updates: Partial<ClipConfig>): boolean;
23
+ /**
24
+ * 移除片段
25
+ */
26
+ removeClip(id: string): boolean;
27
+ /**
28
+ * 移除所有片段
29
+ */
30
+ removeAllClips(): void;
31
+ /**
32
+ * 分割片段
33
+ */
34
+ splitClip(id: string, splitTime: number): {
35
+ clip1: Clip;
36
+ clip2: Clip;
37
+ } | null;
38
+ /**
39
+ * 检查片段是否存在
40
+ */
41
+ exists(id: string): boolean;
42
+ /**
43
+ * 根据时间范围获取片段
44
+ */
45
+ getClipsInTimeRange(startTime: number, endTime: number): Clip[];
46
+ /**
47
+ * 获取片段数量
48
+ */
49
+ getClipCount(): number;
50
+ /**
51
+ * 批量添加片段
52
+ */
53
+ addClips(configs: ClipConfig[]): string[];
54
+ }