@checksub_team/peaks_timeline 2.2.1 → 2.3.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/peaks.js +650 -137
- package/src/components/invoker.js +218 -2
- package/src/components/line-group.js +6 -6
- package/src/components/line-groups.js +40 -3
- package/src/components/source-group.js +152 -64
- package/src/components/sources-layer.js +397 -41
- package/src/components/waveform-builder.js +27 -9
- package/src/utils.js +90 -0
- package/src/view.js +90 -49
|
@@ -10,12 +10,14 @@ define([
|
|
|
10
10
|
'./waveform-builder',
|
|
11
11
|
'./waveform-shape',
|
|
12
12
|
'./loader',
|
|
13
|
+
'./invoker',
|
|
13
14
|
'../utils',
|
|
14
15
|
'konva'
|
|
15
16
|
], function(
|
|
16
17
|
WaveformBuilder,
|
|
17
18
|
WaveformShape,
|
|
18
19
|
Loader,
|
|
20
|
+
Invoker,
|
|
19
21
|
Utils,
|
|
20
22
|
Konva) {
|
|
21
23
|
'use strict';
|
|
@@ -24,7 +26,10 @@ define([
|
|
|
24
26
|
var SPACING_BETWEEN_PREVIEWS = 1.5;
|
|
25
27
|
var CORNER_RADIUS = 8;
|
|
26
28
|
var INDICATOR_RADIUS = 4; // px
|
|
27
|
-
var PREVIEW_CREATE_CHUNK =
|
|
29
|
+
var PREVIEW_CREATE_CHUNK = 12; // number of preview tiles to add per idle slice (increased for better throughput)
|
|
30
|
+
|
|
31
|
+
// Shared invoker for all source groups to coordinate updates
|
|
32
|
+
var sharedInvoker = new Invoker();
|
|
28
33
|
|
|
29
34
|
/**
|
|
30
35
|
* Creates a source group for the given source.
|
|
@@ -47,7 +52,6 @@ define([
|
|
|
47
52
|
|
|
48
53
|
var self = this;
|
|
49
54
|
|
|
50
|
-
this._x = this._view.timeToPixels(source.startTime);
|
|
51
55
|
this._width = this._view.timeToPixels(source.endTime - source.startTime);
|
|
52
56
|
var heights = SourceGroup.getHeights(source, peaks);
|
|
53
57
|
|
|
@@ -59,16 +63,22 @@ define([
|
|
|
59
63
|
this._selected = this._source.selected;
|
|
60
64
|
this._hovered = false;
|
|
61
65
|
this._isDragged = false;
|
|
66
|
+
this._isHandleDragging = false;
|
|
62
67
|
this._destroyed = false;
|
|
63
68
|
|
|
69
|
+
// Performance: track if draw is needed
|
|
70
|
+
this._drawScheduled = false;
|
|
71
|
+
|
|
64
72
|
this._previewList = [];
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
// internal queue state for async preview creation
|
|
74
|
+
this._previewBuildQueue = new Set();
|
|
75
|
+
// Track pending idle callbacks for cleanup
|
|
76
|
+
this._pendingIdleCallbacks = new Set();
|
|
67
77
|
|
|
68
78
|
this._markersGroup = this._createMarkers();
|
|
69
79
|
|
|
70
80
|
this._group = new Konva.Group({
|
|
71
|
-
x: this.
|
|
81
|
+
x: this._view.timeToPixels(source.startTime),
|
|
72
82
|
sourceId: this._source.id,
|
|
73
83
|
draggable: this._source.draggable,
|
|
74
84
|
dragBoundFunc: function() {
|
|
@@ -135,7 +145,7 @@ define([
|
|
|
135
145
|
if (!this._source.loading) {
|
|
136
146
|
this._showButtons();
|
|
137
147
|
}
|
|
138
|
-
|
|
148
|
+
this._scheduleBatchDraw();
|
|
139
149
|
};
|
|
140
150
|
|
|
141
151
|
SourceGroup.prototype._onHoverEnd = function() {
|
|
@@ -143,7 +153,7 @@ define([
|
|
|
143
153
|
this._manualHover = false;
|
|
144
154
|
this._view.setHoveredElement(null);
|
|
145
155
|
this._hideButtons();
|
|
146
|
-
|
|
156
|
+
this._scheduleBatchDraw();
|
|
147
157
|
};
|
|
148
158
|
|
|
149
159
|
SourceGroup.prototype._onDragStart = function(element) {
|
|
@@ -182,7 +192,7 @@ define([
|
|
|
182
192
|
};
|
|
183
193
|
|
|
184
194
|
SourceGroup.prototype.isActive = function() {
|
|
185
|
-
return this._isDragged;
|
|
195
|
+
return this._isDragged || this._isHandleDragging;
|
|
186
196
|
};
|
|
187
197
|
|
|
188
198
|
SourceGroup.prototype.addToContent = function(newChild) {
|
|
@@ -202,14 +212,17 @@ define([
|
|
|
202
212
|
this._rightHandle.x(this._width - handleWidth);
|
|
203
213
|
};
|
|
204
214
|
|
|
205
|
-
SourceGroup.prototype._onSourceGroupHandleDrag = function(draggedElement,
|
|
206
|
-
const diff = this._view.pixelsToTime(dragPos.x - this._mouseDownX);
|
|
207
|
-
const timeOffsetDiff = this._view.getTimeOffset() - this._initialTimeOffset;
|
|
208
|
-
|
|
215
|
+
SourceGroup.prototype._onSourceGroupHandleDrag = function(draggedElement, leftHandle) {
|
|
209
216
|
const { start, end } = this._initialTimes;
|
|
210
217
|
|
|
211
218
|
this._view.updateWithAutoScroll(
|
|
212
219
|
function() {
|
|
220
|
+
var pointer = this._view.getPointerPosition();
|
|
221
|
+
var pointerX = pointer ? pointer.x : this._mouseDownX;
|
|
222
|
+
|
|
223
|
+
const diff = this._view.pixelsToTime(pointerX - this._mouseDownX);
|
|
224
|
+
const timeOffsetDiff = this._view.getTimeOffset() - this._initialTimeOffset;
|
|
225
|
+
|
|
213
226
|
if (this._layer.manageSourceMovements(
|
|
214
227
|
[this._source],
|
|
215
228
|
leftHandle ? start + diff + timeOffsetDiff : null,
|
|
@@ -232,9 +245,13 @@ define([
|
|
|
232
245
|
const frameOffset = this._view.timeToPixels(this._view.getTimeOffset());
|
|
233
246
|
const newTimeToPixelsScale = this._view.getTimeToPixelsScale();
|
|
234
247
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
248
|
+
// When sources are being moved (dragged as blocks), their Konva positions are
|
|
249
|
+
// controlled by the drag handler so they follow the cursor.
|
|
250
|
+
// Auto-scroll triggers frequent `updateSources()` calls; avoid overwriting
|
|
251
|
+
// the drag position here to prevent jitter.
|
|
252
|
+
if (!this._isDragged) {
|
|
253
|
+
this._group.x(startPixel - frameOffset);
|
|
254
|
+
}
|
|
238
255
|
|
|
239
256
|
const newWidth = endPixel - startPixel;
|
|
240
257
|
|
|
@@ -307,14 +324,20 @@ define([
|
|
|
307
324
|
start: this._source.startTime,
|
|
308
325
|
end: this._source.endTime
|
|
309
326
|
};
|
|
310
|
-
this.
|
|
327
|
+
this._isHandleDragging = true;
|
|
311
328
|
|
|
312
329
|
this._hideButtons();
|
|
313
330
|
};
|
|
314
331
|
|
|
315
332
|
SourceGroup.prototype._onHandleDragEnd = function() {
|
|
316
|
-
this.
|
|
333
|
+
this._isHandleDragging = false;
|
|
317
334
|
this._showButtons();
|
|
335
|
+
|
|
336
|
+
// When resizing via handles, drag events no longer bubble to the parent
|
|
337
|
+
// group, so we won't hit the move-drag end path that calls prepareDragEnd.
|
|
338
|
+
// Normalize handle geometry here so handles snap to the updated width.
|
|
339
|
+
this.update();
|
|
340
|
+
this.prepareDragEnd();
|
|
318
341
|
};
|
|
319
342
|
|
|
320
343
|
SourceGroup.prototype._addHandles = function(forceCreate) {
|
|
@@ -328,14 +351,23 @@ define([
|
|
|
328
351
|
height: this._unwrappedHeight,
|
|
329
352
|
visible: true,
|
|
330
353
|
draggable: this._source.resizable,
|
|
331
|
-
dragBoundFunc: function(
|
|
332
|
-
return self._onSourceGroupHandleDrag(this,
|
|
354
|
+
dragBoundFunc: function() {
|
|
355
|
+
return self._onSourceGroupHandleDrag(this, true);
|
|
333
356
|
}
|
|
334
357
|
});
|
|
335
358
|
|
|
336
|
-
|
|
359
|
+
// Prevent handle drag events from bubbling to the parent group.
|
|
360
|
+
// Otherwise the parent SourceGroup dragstart/dragend handlers run and
|
|
361
|
+
// the sources-layer creates move-drag ghosts during resize.
|
|
362
|
+
this._leftHandle.on('dragstart', function(event) {
|
|
363
|
+
event.cancelBubble = true;
|
|
364
|
+
self._onHandleDragStart(event);
|
|
365
|
+
});
|
|
337
366
|
|
|
338
|
-
this._leftHandle.on('dragend',
|
|
367
|
+
this._leftHandle.on('dragend', function(event) {
|
|
368
|
+
event.cancelBubble = true;
|
|
369
|
+
self._onHandleDragEnd(event);
|
|
370
|
+
});
|
|
339
371
|
|
|
340
372
|
if (this._source.resizable) {
|
|
341
373
|
this._leftHandle.on('mouseover', function() {
|
|
@@ -355,14 +387,23 @@ define([
|
|
|
355
387
|
height: this._unwrappedHeight,
|
|
356
388
|
visible: true,
|
|
357
389
|
draggable: this._source.resizable,
|
|
358
|
-
dragBoundFunc: function(
|
|
359
|
-
return self._onSourceGroupHandleDrag(this,
|
|
390
|
+
dragBoundFunc: function() {
|
|
391
|
+
return self._onSourceGroupHandleDrag(this, false);
|
|
360
392
|
}
|
|
361
393
|
});
|
|
362
394
|
|
|
363
|
-
|
|
395
|
+
// Prevent handle drag events from bubbling to the parent group.
|
|
396
|
+
// Otherwise the parent SourceGroup dragstart/dragend handlers run and
|
|
397
|
+
// the sources-layer creates move-drag ghosts during resize.
|
|
398
|
+
this._rightHandle.on('dragstart', function(event) {
|
|
399
|
+
event.cancelBubble = true;
|
|
400
|
+
self._onHandleDragStart(event);
|
|
401
|
+
});
|
|
364
402
|
|
|
365
|
-
this._rightHandle.on('dragend',
|
|
403
|
+
this._rightHandle.on('dragend', function(event) {
|
|
404
|
+
event.cancelBubble = true;
|
|
405
|
+
self._onHandleDragEnd(event);
|
|
406
|
+
});
|
|
366
407
|
|
|
367
408
|
if (this._source.resizable) {
|
|
368
409
|
this._rightHandle.on('mouseover', function() {
|
|
@@ -425,17 +466,20 @@ define([
|
|
|
425
466
|
)
|
|
426
467
|
)
|
|
427
468
|
);
|
|
469
|
+
|
|
470
|
+
var actualX = this._group.x() + this._view.getFrameOffset();
|
|
428
471
|
var x = Math.max(
|
|
429
472
|
0,
|
|
430
|
-
this._view.getFrameOffset() -
|
|
473
|
+
this._view.getFrameOffset() - actualX - 2 * radius
|
|
431
474
|
);
|
|
432
475
|
var width = Math.min(
|
|
433
476
|
this._width - x,
|
|
434
477
|
this._view.getWidth() + 4 * radius - Math.max(
|
|
435
478
|
0,
|
|
436
|
-
|
|
479
|
+
actualX - this._view.getFrameOffset()
|
|
437
480
|
)
|
|
438
481
|
);
|
|
482
|
+
|
|
439
483
|
var xWidth = x + width;
|
|
440
484
|
|
|
441
485
|
if (width > 0) {
|
|
@@ -678,10 +722,6 @@ define([
|
|
|
678
722
|
return this._width;
|
|
679
723
|
};
|
|
680
724
|
|
|
681
|
-
SourceGroup.prototype.getX = function() {
|
|
682
|
-
return this._x;
|
|
683
|
-
};
|
|
684
|
-
|
|
685
725
|
SourceGroup.prototype.getAbsoluteY = function() {
|
|
686
726
|
return this._group.absolutePosition().y;
|
|
687
727
|
};
|
|
@@ -700,6 +740,13 @@ define([
|
|
|
700
740
|
return this._group.y(value);
|
|
701
741
|
};
|
|
702
742
|
|
|
743
|
+
SourceGroup.prototype.absolutePosition = function(value) {
|
|
744
|
+
if (value) {
|
|
745
|
+
return this._group.absolutePosition(value);
|
|
746
|
+
}
|
|
747
|
+
return this._group.absolutePosition();
|
|
748
|
+
};
|
|
749
|
+
|
|
703
750
|
SourceGroup.prototype.getSource = function() {
|
|
704
751
|
return this._source;
|
|
705
752
|
};
|
|
@@ -713,6 +760,10 @@ define([
|
|
|
713
760
|
this._group.fire('mouseleave', { evt: new MouseEvent('mouseleave') }, true);
|
|
714
761
|
};
|
|
715
762
|
|
|
763
|
+
SourceGroup.prototype.setDragging = function(isDragging) {
|
|
764
|
+
this._isDragged = isDragging;
|
|
765
|
+
};
|
|
766
|
+
|
|
716
767
|
SourceGroup.prototype.startDrag = function() {
|
|
717
768
|
return this._group.startDrag();
|
|
718
769
|
};
|
|
@@ -725,12 +776,12 @@ define([
|
|
|
725
776
|
this._group.moveTo(group);
|
|
726
777
|
};
|
|
727
778
|
|
|
728
|
-
SourceGroup.prototype.
|
|
729
|
-
|
|
779
|
+
SourceGroup.prototype.moveToTop = function() {
|
|
780
|
+
this._group.moveToTop();
|
|
730
781
|
};
|
|
731
782
|
|
|
732
|
-
SourceGroup.prototype.
|
|
733
|
-
|
|
783
|
+
SourceGroup.prototype.isDescendantOf = function(group) {
|
|
784
|
+
return group.isAncestorOf(this._group);
|
|
734
785
|
};
|
|
735
786
|
|
|
736
787
|
SourceGroup.prototype.getParent = function() {
|
|
@@ -878,19 +929,38 @@ define([
|
|
|
878
929
|
};
|
|
879
930
|
|
|
880
931
|
SourceGroup.prototype._createAudioPreview = function(preview, redraw) {
|
|
932
|
+
var url = preview.url;
|
|
933
|
+
|
|
934
|
+
// Resample the waveform data for the current zoom level if needed
|
|
935
|
+
var scaledData = this._layer.getLoadedData(url + '-scaled');
|
|
936
|
+
var currentScale = this._view.getTimeToPixelsScale();
|
|
937
|
+
|
|
938
|
+
if (scaledData && scaledData.scale !== currentScale) {
|
|
939
|
+
var originalData = this._layer.getLoadedData(url);
|
|
940
|
+
|
|
941
|
+
if (originalData) {
|
|
942
|
+
this._layer.setLoadedData(url + '-scaled', {
|
|
943
|
+
data: originalData.resample({
|
|
944
|
+
scale: originalData.sample_rate / currentScale
|
|
945
|
+
}),
|
|
946
|
+
scale: currentScale
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
881
951
|
var waveform = new WaveformShape({
|
|
882
952
|
layer: this._layer,
|
|
883
953
|
view: this._view,
|
|
884
954
|
source: this._source,
|
|
885
955
|
height: preview.group.height(),
|
|
886
|
-
url:
|
|
956
|
+
url: url
|
|
887
957
|
});
|
|
888
958
|
|
|
889
959
|
preview.group.add(waveform);
|
|
890
960
|
this._addToUnwrap(preview.group);
|
|
891
961
|
|
|
892
962
|
if (redraw) {
|
|
893
|
-
this.
|
|
963
|
+
this._scheduleBatchDraw();
|
|
894
964
|
}
|
|
895
965
|
|
|
896
966
|
this._previewList.push(preview);
|
|
@@ -991,6 +1061,26 @@ define([
|
|
|
991
1061
|
});
|
|
992
1062
|
};
|
|
993
1063
|
|
|
1064
|
+
/**
|
|
1065
|
+
* Schedules a batch draw using RAF to coalesce multiple draw requests.
|
|
1066
|
+
* This is more efficient than calling _batchDraw directly.
|
|
1067
|
+
*/
|
|
1068
|
+
SourceGroup.prototype._scheduleBatchDraw = function() {
|
|
1069
|
+
if (this._destroyed || this._drawScheduled) {
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
this._drawScheduled = true;
|
|
1074
|
+
var self = this;
|
|
1075
|
+
|
|
1076
|
+
sharedInvoker.scheduleFrame(function() {
|
|
1077
|
+
self._drawScheduled = false;
|
|
1078
|
+
if (!self._destroyed) {
|
|
1079
|
+
self._batchDraw();
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
};
|
|
1083
|
+
|
|
994
1084
|
SourceGroup.prototype._batchDraw = function() {
|
|
995
1085
|
var layer = this._group && this._group.getLayer && this._group.getLayer();
|
|
996
1086
|
|
|
@@ -999,31 +1089,16 @@ define([
|
|
|
999
1089
|
}
|
|
1000
1090
|
};
|
|
1001
1091
|
|
|
1002
|
-
// Utility to schedule work during idle time
|
|
1092
|
+
// Utility to schedule work during idle time, with tracking for cleanup
|
|
1003
1093
|
SourceGroup.prototype._scheduleIdle = function(fn) {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
return window.requestAnimationFrame(function() {
|
|
1010
|
-
fn({
|
|
1011
|
-
timeRemaining: function() {
|
|
1012
|
-
return 0;
|
|
1013
|
-
},
|
|
1014
|
-
didTimeout: true
|
|
1015
|
-
});
|
|
1016
|
-
});
|
|
1017
|
-
}
|
|
1094
|
+
var self = this;
|
|
1095
|
+
var id = Utils.scheduleIdle(function(deadline) {
|
|
1096
|
+
self._pendingIdleCallbacks.delete(id);
|
|
1097
|
+
fn(deadline);
|
|
1098
|
+
}, { timeout: 50 });
|
|
1018
1099
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
timeRemaining: function() {
|
|
1022
|
-
return 0;
|
|
1023
|
-
},
|
|
1024
|
-
didTimeout: true
|
|
1025
|
-
});
|
|
1026
|
-
}, 0);
|
|
1100
|
+
this._pendingIdleCallbacks.add(id);
|
|
1101
|
+
return id;
|
|
1027
1102
|
};
|
|
1028
1103
|
|
|
1029
1104
|
SourceGroup.prototype._ensureImagePreviewCount = function(preview, targetCount, interImageSpacing) {
|
|
@@ -1038,7 +1113,7 @@ define([
|
|
|
1038
1113
|
}
|
|
1039
1114
|
|
|
1040
1115
|
if (currentCount >= targetCount || this._previewBuildQueue.has(preview)) {
|
|
1041
|
-
this.
|
|
1116
|
+
this._scheduleBatchDraw();
|
|
1042
1117
|
return;
|
|
1043
1118
|
}
|
|
1044
1119
|
|
|
@@ -1066,7 +1141,7 @@ define([
|
|
|
1066
1141
|
added += 1;
|
|
1067
1142
|
}
|
|
1068
1143
|
|
|
1069
|
-
self.
|
|
1144
|
+
self._scheduleBatchDraw();
|
|
1070
1145
|
|
|
1071
1146
|
if (nextIndex < targetCount) {
|
|
1072
1147
|
self._scheduleIdle(buildChunk);
|
|
@@ -1111,7 +1186,7 @@ define([
|
|
|
1111
1186
|
this._ensureImagePreviewCount(preview, targetCount, interImageSpacing);
|
|
1112
1187
|
|
|
1113
1188
|
if (redraw) {
|
|
1114
|
-
this.
|
|
1189
|
+
this._scheduleBatchDraw();
|
|
1115
1190
|
}
|
|
1116
1191
|
|
|
1117
1192
|
this._previewList.push(preview);
|
|
@@ -1684,7 +1759,7 @@ define([
|
|
|
1684
1759
|
self._source.volume = Math.max(self._source.volumeRange[0], Math.min(volume, self._source.volumeRange[1]));
|
|
1685
1760
|
self._peaks.emit('source.volumeChanged', self._source);
|
|
1686
1761
|
|
|
1687
|
-
|
|
1762
|
+
self._scheduleBatchDraw();
|
|
1688
1763
|
});
|
|
1689
1764
|
|
|
1690
1765
|
volumeSliderGroup.on('dragend', function() {
|
|
@@ -1707,6 +1782,19 @@ define([
|
|
|
1707
1782
|
};
|
|
1708
1783
|
|
|
1709
1784
|
SourceGroup.prototype.destroy = function() {
|
|
1785
|
+
// Cancel any pending idle callbacks to prevent memory leaks
|
|
1786
|
+
if (this._pendingIdleCallbacks) {
|
|
1787
|
+
this._pendingIdleCallbacks.forEach(function(id) {
|
|
1788
|
+
Utils.cancelIdle(id);
|
|
1789
|
+
});
|
|
1790
|
+
this._pendingIdleCallbacks.clear();
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
// Clear preview build queue
|
|
1794
|
+
if (this._previewBuildQueue) {
|
|
1795
|
+
this._previewBuildQueue.clear();
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1710
1798
|
if (this._buttonsAnimation) {
|
|
1711
1799
|
this._buttonsAnimation.destroy();
|
|
1712
1800
|
this._buttonsAnimation = null;
|