wavesurfer.js 7.0.0-alpha.17 → 7.0.0-alpha.19
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/dist/base-plugin.d.ts +2 -2
- package/dist/base-plugin.js +5 -1
- package/dist/decoder.js +7 -19
- package/dist/event-emitter.js +1 -0
- package/dist/fetcher.js +2 -13
- package/dist/index.d.ts +4 -2
- package/dist/index.js +38 -42
- package/dist/player.d.ts +1 -0
- package/dist/player.js +16 -15
- package/dist/plugins/minimap.js +17 -6
- package/dist/plugins/multitrack.d.ts +10 -2
- package/dist/plugins/multitrack.js +70 -35
- package/dist/plugins/regions.js +61 -66
- package/dist/plugins/timeline.js +6 -6
- package/dist/renderer.d.ts +4 -6
- package/dist/renderer.js +134 -147
- package/dist/timer.js +1 -4
- package/dist/wavesurfer.Minimap.min.js +1 -1
- package/dist/wavesurfer.Multitrack.min.js +1 -1
- package/dist/wavesurfer.Regions.min.js +1 -1
- package/dist/wavesurfer.Timeline.min.js +1 -1
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +29 -25
- package/dist/player-webaudio.d.ts +0 -8
- package/dist/player-webaudio.js +0 -32
- package/dist/plugins/xmultitrack.d.ts +0 -44
- package/dist/plugins/xmultitrack.js +0 -260
- package/dist/react/useWavesurfer.d.ts +0 -5
- package/dist/react/useWavesurfer.js +0 -20
package/dist/plugins/regions.js
CHANGED
|
@@ -16,60 +16,21 @@ const el = (tagName, css) => {
|
|
|
16
16
|
return element;
|
|
17
17
|
};
|
|
18
18
|
class RegionsPlugin extends BasePlugin {
|
|
19
|
+
dragStart = NaN;
|
|
20
|
+
regionsContainer;
|
|
21
|
+
regions = [];
|
|
22
|
+
createdRegion = null;
|
|
23
|
+
modifiedRegion = null;
|
|
24
|
+
isResizingLeft = false;
|
|
25
|
+
isMoving = false;
|
|
19
26
|
/** Create an instance of RegionsPlugin */
|
|
20
27
|
constructor(params, options) {
|
|
21
28
|
super(params, options);
|
|
22
|
-
this.dragStart = NaN;
|
|
23
|
-
this.regions = [];
|
|
24
|
-
this.createdRegion = null;
|
|
25
|
-
this.modifiedRegion = null;
|
|
26
|
-
this.isResizingLeft = false;
|
|
27
|
-
this.isMoving = false;
|
|
28
|
-
this.handleMouseDown = (e) => {
|
|
29
|
-
if (this.options.draggable || this.options.resizable || this.options.dragSelection) {
|
|
30
|
-
this.dragStart = e.clientX - this.wrapper.getBoundingClientRect().left;
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
this.handleMouseMove = (e) => {
|
|
34
|
-
const dragEnd = e.clientX - this.wrapper.getBoundingClientRect().left;
|
|
35
|
-
if (this.options.draggable && this.modifiedRegion && this.isMoving) {
|
|
36
|
-
this.moveRegion(this.modifiedRegion, dragEnd - this.dragStart);
|
|
37
|
-
this.dragStart = dragEnd;
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
if (this.options.resizable && this.modifiedRegion) {
|
|
41
|
-
this.updateRegion(this.modifiedRegion, this.isResizingLeft ? dragEnd : undefined, this.isResizingLeft ? undefined : dragEnd);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
if (this.options.dragSelection && !isNaN(this.dragStart)) {
|
|
45
|
-
const dragEnd = e.clientX - this.regionsContainer.getBoundingClientRect().left;
|
|
46
|
-
if (dragEnd - this.dragStart >= MIN_WIDTH) {
|
|
47
|
-
if (!this.createdRegion) {
|
|
48
|
-
this.wrapper.style.pointerEvents = 'none';
|
|
49
|
-
this.createdRegion = this.createRegion(this.dragStart, dragEnd);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
this.updateRegion(this.createdRegion, this.dragStart, dragEnd);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
this.handleMouseUp = () => {
|
|
58
|
-
if (this.createdRegion) {
|
|
59
|
-
this.addRegion(this.createdRegion);
|
|
60
|
-
this.createdRegion = null;
|
|
61
|
-
}
|
|
62
|
-
this.modifiedRegion = null;
|
|
63
|
-
this.isMoving = false;
|
|
64
|
-
this.dragStart = NaN;
|
|
65
|
-
this.wrapper.style.pointerEvents = '';
|
|
66
|
-
};
|
|
67
29
|
this.options = Object.assign({}, defaultOptions, options);
|
|
68
30
|
this.regionsContainer = this.initRegionsContainer();
|
|
69
|
-
|
|
31
|
+
this.subscriptions.push(this.wavesurfer.once('decode', () => {
|
|
70
32
|
this.wrapper.appendChild(this.regionsContainer);
|
|
71
|
-
});
|
|
72
|
-
this.subscriptions.push(unsubscribe);
|
|
33
|
+
}));
|
|
73
34
|
this.wrapper.addEventListener('mousedown', this.handleMouseDown);
|
|
74
35
|
document.addEventListener('mousemove', this.handleMouseMove);
|
|
75
36
|
document.addEventListener('mouseup', this.handleMouseUp);
|
|
@@ -93,12 +54,53 @@ class RegionsPlugin extends BasePlugin {
|
|
|
93
54
|
pointerEvents: 'none',
|
|
94
55
|
});
|
|
95
56
|
}
|
|
57
|
+
handleMouseDown = (e) => {
|
|
58
|
+
if (this.options.draggable || this.options.resizable || this.options.dragSelection) {
|
|
59
|
+
this.dragStart = e.clientX - this.wrapper.getBoundingClientRect().left;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
handleMouseMove = (e) => {
|
|
63
|
+
const box = this.wrapper.getBoundingClientRect();
|
|
64
|
+
const { width } = box;
|
|
65
|
+
const dragEnd = e.clientX - box.left;
|
|
66
|
+
if (this.options.draggable && this.modifiedRegion && this.isMoving) {
|
|
67
|
+
this.moveRegion(this.modifiedRegion, (dragEnd - this.dragStart) / width);
|
|
68
|
+
this.dragStart = dragEnd;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (this.options.resizable && this.modifiedRegion) {
|
|
72
|
+
this.updateRegion(this.modifiedRegion, this.isResizingLeft ? dragEnd / width : undefined, this.isResizingLeft ? undefined : dragEnd / width);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (this.options.dragSelection && !isNaN(this.dragStart)) {
|
|
76
|
+
const dragEnd = e.clientX - this.regionsContainer.getBoundingClientRect().left;
|
|
77
|
+
if (dragEnd - this.dragStart >= MIN_WIDTH) {
|
|
78
|
+
if (!this.createdRegion) {
|
|
79
|
+
this.wrapper.style.pointerEvents = 'none';
|
|
80
|
+
this.createdRegion = this.createRegion(this.dragStart / width, dragEnd / width);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
this.updateRegion(this.createdRegion, this.dragStart / width, dragEnd / width);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
handleMouseUp = () => {
|
|
89
|
+
if (this.createdRegion) {
|
|
90
|
+
this.addRegion(this.createdRegion);
|
|
91
|
+
this.createdRegion = null;
|
|
92
|
+
}
|
|
93
|
+
this.modifiedRegion = null;
|
|
94
|
+
this.isMoving = false;
|
|
95
|
+
this.dragStart = NaN;
|
|
96
|
+
this.wrapper.style.pointerEvents = '';
|
|
97
|
+
};
|
|
96
98
|
createRegionElement(start, end, title = '') {
|
|
97
99
|
const noWidth = start === end;
|
|
98
100
|
const div = el('div', {
|
|
99
101
|
position: 'absolute',
|
|
100
|
-
left: `${start}
|
|
101
|
-
width: `${end - start}
|
|
102
|
+
left: `${start * 100}%`,
|
|
103
|
+
width: `${(end - start) * 100}%`,
|
|
102
104
|
height: '100%',
|
|
103
105
|
backgroundColor: noWidth ? '' : 'rgba(0, 0, 0, 0.1)',
|
|
104
106
|
borderRadius: '2px',
|
|
@@ -165,15 +167,12 @@ class RegionsPlugin extends BasePlugin {
|
|
|
165
167
|
}
|
|
166
168
|
createRegion(start, end, title = '') {
|
|
167
169
|
const duration = this.wavesurfer.getDuration();
|
|
168
|
-
const width = this.regionsContainer.clientWidth;
|
|
169
|
-
start = Math.max(0, Math.min(start, width - 1));
|
|
170
|
-
end = Math.max(0, Math.min(end, width - 1));
|
|
171
170
|
return {
|
|
172
171
|
element: this.createRegionElement(start, end, title),
|
|
173
172
|
start,
|
|
174
173
|
end,
|
|
175
|
-
startTime:
|
|
176
|
-
endTime:
|
|
174
|
+
startTime: start * duration,
|
|
175
|
+
endTime: end * duration,
|
|
177
176
|
title,
|
|
178
177
|
};
|
|
179
178
|
}
|
|
@@ -182,19 +181,16 @@ class RegionsPlugin extends BasePlugin {
|
|
|
182
181
|
this.emit('region-created', { region });
|
|
183
182
|
}
|
|
184
183
|
updateRegion(region, start, end) {
|
|
185
|
-
const width = this.regionsContainer.clientWidth;
|
|
186
184
|
if (start != null) {
|
|
187
|
-
start = Math.max(0, Math.min(start, width));
|
|
188
185
|
region.start = start;
|
|
189
|
-
region.element.style.left = `${region.start}
|
|
190
|
-
region.element.style.width = `${region.end - region.start}
|
|
191
|
-
region.startTime =
|
|
186
|
+
region.element.style.left = `${region.start * 100}%`;
|
|
187
|
+
region.element.style.width = `${(region.end - region.start) * 100}%`;
|
|
188
|
+
region.startTime = start * this.wavesurfer.getDuration();
|
|
192
189
|
}
|
|
193
190
|
if (end != null) {
|
|
194
|
-
end = Math.max(0, Math.min(end, width));
|
|
195
191
|
region.end = end;
|
|
196
|
-
region.element.style.width = `${region.end - region.start}
|
|
197
|
-
region.endTime =
|
|
192
|
+
region.element.style.width = `${(region.end - region.start) * 100}%`;
|
|
193
|
+
region.endTime = end * this.wavesurfer.getDuration();
|
|
198
194
|
}
|
|
199
195
|
this.emit('region-updated', { region });
|
|
200
196
|
}
|
|
@@ -204,9 +200,8 @@ class RegionsPlugin extends BasePlugin {
|
|
|
204
200
|
/** Create a region at a given start and end time, with an optional title */
|
|
205
201
|
add(startTime, endTime, title = '', color = '') {
|
|
206
202
|
const duration = this.wavesurfer.getDuration();
|
|
207
|
-
const
|
|
208
|
-
const
|
|
209
|
-
const end = (endTime / duration) * width;
|
|
203
|
+
const start = startTime / duration;
|
|
204
|
+
const end = endTime / duration;
|
|
210
205
|
const region = this.createRegion(start, end, title);
|
|
211
206
|
this.addRegion(region);
|
|
212
207
|
if (color)
|
package/dist/plugins/timeline.js
CHANGED
|
@@ -3,11 +3,12 @@ const defaultOptions = {
|
|
|
3
3
|
height: 20,
|
|
4
4
|
};
|
|
5
5
|
class TimelinePlugin extends BasePlugin {
|
|
6
|
+
timelineWrapper;
|
|
7
|
+
options;
|
|
6
8
|
constructor(params, options) {
|
|
7
|
-
var _a;
|
|
8
9
|
super(params, options);
|
|
9
10
|
this.options = Object.assign({}, defaultOptions, options);
|
|
10
|
-
this.container =
|
|
11
|
+
this.container = this.options.container ?? this.wrapper;
|
|
11
12
|
this.timelineWrapper = this.initTimelineWrapper();
|
|
12
13
|
this.container.appendChild(this.timelineWrapper);
|
|
13
14
|
if (this.options.duration) {
|
|
@@ -78,12 +79,11 @@ class TimelinePlugin extends BasePlugin {
|
|
|
78
79
|
return 2;
|
|
79
80
|
}
|
|
80
81
|
initTimeline(duration) {
|
|
81
|
-
var _a, _b, _c;
|
|
82
82
|
const width = Math.round(this.timelineWrapper.scrollWidth * devicePixelRatio);
|
|
83
83
|
const pxPerSec = width / duration;
|
|
84
|
-
const timeInterval =
|
|
85
|
-
const primaryLabelInterval =
|
|
86
|
-
const secondaryLabelInterval =
|
|
84
|
+
const timeInterval = this.options.timeInterval ?? this.defaultTimeInterval(pxPerSec);
|
|
85
|
+
const primaryLabelInterval = this.options.primaryLabelInterval ?? this.defaultPrimaryLabelInterval(pxPerSec);
|
|
86
|
+
const secondaryLabelInterval = this.options.secondaryLabelInterval ?? this.defaultSecondaryLabelInterval(pxPerSec);
|
|
87
87
|
const timeline = document.createElement('div');
|
|
88
88
|
timeline.setAttribute('style', `
|
|
89
89
|
height: ${this.options.height}px;
|
package/dist/renderer.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ type RendererOptions = {
|
|
|
11
11
|
barWidth?: number;
|
|
12
12
|
barGap?: number;
|
|
13
13
|
barRadius?: number;
|
|
14
|
+
noScrollbar?: boolean;
|
|
14
15
|
};
|
|
15
16
|
type RendererEvents = {
|
|
16
17
|
click: {
|
|
@@ -22,18 +23,15 @@ declare class Renderer extends EventEmitter<RendererEvents> {
|
|
|
22
23
|
private container;
|
|
23
24
|
private scrollContainer;
|
|
24
25
|
private wrapper;
|
|
25
|
-
private
|
|
26
|
-
private
|
|
27
|
-
private cursor;
|
|
28
|
-
private ctx;
|
|
26
|
+
private canvasWrapper;
|
|
27
|
+
private progressWrapper;
|
|
29
28
|
private timeout;
|
|
30
29
|
constructor(options: RendererOptions);
|
|
31
|
-
getContainer():
|
|
30
|
+
getContainer(): HTMLElement;
|
|
32
31
|
getWrapper(): HTMLElement;
|
|
33
32
|
destroy(): void;
|
|
34
33
|
private delay;
|
|
35
34
|
private renderPeaks;
|
|
36
|
-
private createProgressMask;
|
|
37
35
|
render(audioData: AudioBuffer): void;
|
|
38
36
|
zoom(audioData: AudioBuffer, minPxPerSec: number): void;
|
|
39
37
|
renderProgress(progress: number, autoCenter?: boolean): void;
|
package/dist/renderer.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
import EventEmitter from './event-emitter.js';
|
|
11
2
|
class Renderer extends EventEmitter {
|
|
3
|
+
options;
|
|
4
|
+
container;
|
|
5
|
+
scrollContainer;
|
|
6
|
+
wrapper;
|
|
7
|
+
canvasWrapper;
|
|
8
|
+
progressWrapper;
|
|
9
|
+
timeout = null;
|
|
12
10
|
constructor(options) {
|
|
13
11
|
super();
|
|
14
|
-
this.timeout = null;
|
|
15
12
|
this.options = options;
|
|
16
13
|
let container = null;
|
|
17
14
|
if (typeof this.options.container === 'string') {
|
|
@@ -35,64 +32,65 @@ class Renderer extends EventEmitter {
|
|
|
35
32
|
overflow-y: hidden;
|
|
36
33
|
width: 100%;
|
|
37
34
|
position: relative;
|
|
35
|
+
${this.options.noScrollbar ? 'scrollbar-color: transparent;' : ''}
|
|
36
|
+
}
|
|
37
|
+
:host ::-webkit-scrollbar {
|
|
38
|
+
display: ${this.options.noScrollbar ? 'none' : 'auto'};
|
|
38
39
|
}
|
|
39
40
|
:host .wrapper {
|
|
40
41
|
position: relative;
|
|
41
|
-
width: fit-content;
|
|
42
42
|
min-width: 100%;
|
|
43
43
|
z-index: 2;
|
|
44
44
|
}
|
|
45
|
+
:host .canvases {
|
|
46
|
+
position: relative;
|
|
47
|
+
height: ${this.options.height}px;
|
|
48
|
+
}
|
|
45
49
|
:host canvas {
|
|
46
50
|
display: block;
|
|
51
|
+
position: absolute;
|
|
52
|
+
top: 0;
|
|
47
53
|
height: ${this.options.height}px;
|
|
48
|
-
min-width: 100%;
|
|
49
54
|
image-rendering: pixelated;
|
|
50
55
|
}
|
|
51
56
|
:host .progress {
|
|
52
|
-
position: absolute;
|
|
53
|
-
z-index: 2;
|
|
54
|
-
top: 0;
|
|
55
|
-
left: 0;
|
|
56
|
-
width: 100%;
|
|
57
57
|
pointer-events: none;
|
|
58
|
-
clip-path: inset(0px 100% 0px 0px);
|
|
59
|
-
}
|
|
60
|
-
:host .cursor {
|
|
61
58
|
position: absolute;
|
|
62
|
-
z-index:
|
|
59
|
+
z-index: 2;
|
|
63
60
|
top: 0;
|
|
64
61
|
left: 0;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
width: 0;
|
|
63
|
+
height: ${this.options.height}px;
|
|
64
|
+
overflow: hidden;
|
|
65
|
+
box-sizing: border-box;
|
|
66
|
+
border-right-style: solid;
|
|
67
|
+
border-right-width: ${this.options.cursorWidth}px;
|
|
68
|
+
border-right-color: ${this.options.cursorColor || this.options.progressColor};
|
|
68
69
|
}
|
|
69
70
|
</style>
|
|
70
71
|
|
|
71
72
|
<div class="scroll">
|
|
72
73
|
<div class="wrapper">
|
|
73
|
-
<
|
|
74
|
-
<
|
|
75
|
-
<div class="cursor"></div>
|
|
74
|
+
<div class="canvases"></div>
|
|
75
|
+
<div class="progress"></div>
|
|
76
76
|
</div>
|
|
77
77
|
</div>
|
|
78
78
|
`;
|
|
79
79
|
this.container = div;
|
|
80
80
|
this.scrollContainer = shadow.querySelector('.scroll');
|
|
81
81
|
this.wrapper = shadow.querySelector('.wrapper');
|
|
82
|
-
this.
|
|
83
|
-
this.
|
|
84
|
-
this.progressCanvas = shadow.querySelector('.progress');
|
|
85
|
-
this.cursor = shadow.querySelector('.cursor');
|
|
82
|
+
this.canvasWrapper = shadow.querySelector('.canvases');
|
|
83
|
+
this.progressWrapper = shadow.querySelector('.progress');
|
|
86
84
|
container.appendChild(div);
|
|
87
|
-
this.
|
|
88
|
-
const rect = this.
|
|
85
|
+
this.wrapper.addEventListener('click', (e) => {
|
|
86
|
+
const rect = this.wrapper.getBoundingClientRect();
|
|
89
87
|
const x = e.clientX - rect.left;
|
|
90
88
|
const relativeX = x / rect.width;
|
|
91
89
|
this.emit('click', { relativeX });
|
|
92
90
|
});
|
|
93
91
|
}
|
|
94
92
|
getContainer() {
|
|
95
|
-
return this.
|
|
93
|
+
return this.scrollContainer;
|
|
96
94
|
}
|
|
97
95
|
getWrapper() {
|
|
98
96
|
return this.wrapper;
|
|
@@ -100,7 +98,7 @@ class Renderer extends EventEmitter {
|
|
|
100
98
|
destroy() {
|
|
101
99
|
this.container.remove();
|
|
102
100
|
}
|
|
103
|
-
delay(fn, delayMs =
|
|
101
|
+
delay(fn, delayMs = 10) {
|
|
104
102
|
if (this.timeout) {
|
|
105
103
|
clearTimeout(this.timeout);
|
|
106
104
|
}
|
|
@@ -110,139 +108,128 @@ class Renderer extends EventEmitter {
|
|
|
110
108
|
}, delayMs);
|
|
111
109
|
});
|
|
112
110
|
}
|
|
113
|
-
renderPeaks(channelData, width, height) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
111
|
+
async renderPeaks(channelData, width, height, pixelRatio) {
|
|
112
|
+
const barWidth = this.options.barWidth != null ? this.options.barWidth * pixelRatio : 1;
|
|
113
|
+
const barGap = this.options.barGap != null ? this.options.barGap * pixelRatio : this.options.barWidth ? barWidth / 2 : 0;
|
|
114
|
+
const barRadius = this.options.barRadius ?? 0;
|
|
115
|
+
const leftChannel = channelData[0];
|
|
116
|
+
const len = leftChannel.length;
|
|
117
|
+
const barCount = Math.floor(width / (barWidth + barGap));
|
|
118
|
+
const barIndexScale = barCount / len;
|
|
119
|
+
const halfHeight = height / 2;
|
|
120
|
+
const isMono = channelData.length === 1;
|
|
121
|
+
const rightChannel = isMono ? leftChannel : channelData[1];
|
|
122
|
+
const useNegative = isMono && rightChannel.some((v) => v < 0);
|
|
123
|
+
const draw = (start, end) => {
|
|
124
|
+
let prevX = 0;
|
|
125
|
+
let prevLeft = 0;
|
|
126
|
+
let prevRight = 0;
|
|
127
|
+
const canvas = document.createElement('canvas');
|
|
128
|
+
canvas.width = Math.round((width * (end - start)) / len);
|
|
129
|
+
canvas.height = this.options.height;
|
|
130
|
+
canvas.style.width = `${Math.floor(canvas.width / pixelRatio)}px`;
|
|
131
|
+
canvas.style.left = `${Math.floor((start * width) / pixelRatio / len)}px`;
|
|
132
|
+
this.canvasWrapper.appendChild(canvas);
|
|
133
|
+
const ctx = canvas.getContext('2d', {
|
|
134
|
+
desynchronized: true,
|
|
135
|
+
});
|
|
136
|
+
ctx.beginPath();
|
|
137
|
+
ctx.fillStyle = this.options.waveColor;
|
|
138
|
+
// Firefox shim until 2023.04.11
|
|
139
|
+
if (!ctx.roundRect)
|
|
140
|
+
ctx.roundRect = ctx.fillRect;
|
|
141
|
+
for (let i = start; i < end; i++) {
|
|
142
|
+
const barIndex = Math.round((i - start) * barIndexScale);
|
|
143
|
+
if (barIndex > prevX) {
|
|
144
|
+
const leftBarHeight = Math.round(prevLeft * halfHeight);
|
|
145
|
+
const rightBarHeight = Math.round(prevRight * halfHeight);
|
|
146
|
+
ctx.roundRect(prevX * (barWidth + barGap), halfHeight - leftBarHeight, barWidth, leftBarHeight + rightBarHeight || 1, barRadius);
|
|
147
|
+
prevX = barIndex;
|
|
148
|
+
prevLeft = 0;
|
|
149
|
+
prevRight = 0;
|
|
150
|
+
}
|
|
151
|
+
const leftValue = useNegative ? leftChannel[i] : Math.abs(leftChannel[i]);
|
|
152
|
+
const rightValue = useNegative ? rightChannel[i] : Math.abs(rightChannel[i]);
|
|
153
|
+
if (leftValue > prevLeft) {
|
|
154
|
+
prevLeft = leftValue;
|
|
155
|
+
}
|
|
156
|
+
// If stereo, both channels are drawn as max values
|
|
157
|
+
// If mono with negative values, the bottom channel will be the min negative values
|
|
158
|
+
if (useNegative ? rightValue < -prevRight : rightValue > prevRight) {
|
|
159
|
+
prevRight = rightValue < 0 ? -rightValue : rightValue;
|
|
158
160
|
}
|
|
159
|
-
ctx.fill();
|
|
160
|
-
ctx.closePath();
|
|
161
|
-
};
|
|
162
|
-
// Clear the canvas
|
|
163
|
-
ctx.clearRect(0, 0, width, height);
|
|
164
|
-
// Draw the currently visible part of the waveform
|
|
165
|
-
const { scrollLeft, scrollWidth, clientWidth } = this.scrollContainer;
|
|
166
|
-
const scale = len / scrollWidth;
|
|
167
|
-
const start = Math.floor(scrollLeft * scale);
|
|
168
|
-
const end = Math.ceil((scrollLeft + clientWidth) * scale);
|
|
169
|
-
draw(start, end);
|
|
170
|
-
// Draw the progress mask
|
|
171
|
-
this.createProgressMask();
|
|
172
|
-
// Draw the rest of the waveform with a timeout for better performance
|
|
173
|
-
if (start > 0) {
|
|
174
|
-
yield this.delay(() => {
|
|
175
|
-
draw(0, start);
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
if (end < len) {
|
|
179
|
-
yield this.delay(() => {
|
|
180
|
-
draw(end, len);
|
|
181
|
-
});
|
|
182
161
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
162
|
+
ctx.fill();
|
|
163
|
+
ctx.closePath();
|
|
164
|
+
// Draw a progress canvas
|
|
165
|
+
const progressCanvas = canvas.cloneNode();
|
|
166
|
+
this.progressWrapper.appendChild(progressCanvas);
|
|
167
|
+
const progressCtx = progressCanvas.getContext('2d', {
|
|
168
|
+
desynchronized: true,
|
|
186
169
|
});
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
170
|
+
progressCtx.drawImage(canvas, 0, 0);
|
|
171
|
+
// Set the composition method to draw only where the waveform is drawn
|
|
172
|
+
progressCtx.globalCompositeOperation = 'source-in';
|
|
173
|
+
progressCtx.fillStyle = this.options.progressColor;
|
|
174
|
+
// This rectangle acts as a mask thanks to the composition method
|
|
175
|
+
progressCtx.fillRect(0, 0, canvas.width, canvas.height);
|
|
176
|
+
};
|
|
177
|
+
// Clear the canvas
|
|
178
|
+
this.canvasWrapper.innerHTML = '';
|
|
179
|
+
this.progressWrapper.innerHTML = '';
|
|
180
|
+
// Draw the currently visible part of the waveform
|
|
181
|
+
const { scrollLeft, scrollWidth, clientWidth } = this.scrollContainer;
|
|
182
|
+
const scale = len / scrollWidth;
|
|
183
|
+
const start = Math.floor(scrollLeft * scale);
|
|
184
|
+
const end = Math.ceil(Math.min(scrollWidth, scrollLeft + clientWidth) * scale);
|
|
185
|
+
draw(start, end);
|
|
186
|
+
// Draw the rest of the waveform with a timeout for better performance
|
|
187
|
+
const step = end - start;
|
|
188
|
+
for (let i = end; i < len; i += step) {
|
|
189
|
+
await this.delay(() => {
|
|
190
|
+
draw(i, Math.min(len, i + step));
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
for (let i = start - 1; i >= 0; i -= step) {
|
|
194
|
+
await this.delay(() => {
|
|
195
|
+
draw(Math.max(0, i - step), i);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
202
198
|
}
|
|
203
199
|
render(audioData) {
|
|
204
200
|
// Determine the width of the canvas
|
|
205
|
-
const
|
|
206
|
-
const parentWidth = this.options.fillParent ? this.scrollContainer.clientWidth *
|
|
201
|
+
const pixelRatio = window.devicePixelRatio || 1;
|
|
202
|
+
const parentWidth = this.options.fillParent ? this.scrollContainer.clientWidth * pixelRatio : 0;
|
|
207
203
|
const scrollWidth = audioData.duration * this.options.minPxPerSec;
|
|
208
204
|
const isScrolling = scrollWidth > parentWidth;
|
|
209
205
|
const width = Math.max(1, isScrolling ? scrollWidth : parentWidth);
|
|
210
206
|
const { height } = this.options;
|
|
211
|
-
|
|
212
|
-
this.
|
|
213
|
-
this.
|
|
207
|
+
// Remember the current cursor position
|
|
208
|
+
const oldCursorPosition = this.progressWrapper.clientWidth;
|
|
209
|
+
this.scrollContainer.style.overflowX = isScrolling ? 'auto' : 'hidden';
|
|
210
|
+
this.wrapper.style.width = `${Math.floor(width / pixelRatio)}px`;
|
|
211
|
+
// Adjust the scroll position so that the cursor stays in the same place
|
|
212
|
+
const newCursortPosition = this.progressWrapper.clientWidth;
|
|
213
|
+
this.scrollContainer.scrollLeft += newCursortPosition - oldCursorPosition;
|
|
214
214
|
// First two channels are used
|
|
215
215
|
const channelData = [audioData.getChannelData(0)];
|
|
216
216
|
if (audioData.numberOfChannels > 1) {
|
|
217
217
|
channelData.push(audioData.getChannelData(1));
|
|
218
218
|
}
|
|
219
|
-
this.renderPeaks(channelData, width, height);
|
|
219
|
+
this.renderPeaks(channelData, width, height, pixelRatio);
|
|
220
220
|
}
|
|
221
221
|
zoom(audioData, minPxPerSec) {
|
|
222
|
-
// Remember the current cursor position
|
|
223
|
-
const oldCursorPosition = this.cursor.getBoundingClientRect().left;
|
|
224
222
|
this.options.minPxPerSec = minPxPerSec;
|
|
225
223
|
this.render(audioData);
|
|
226
|
-
// Adjust the scroll position so that the cursor stays in the same place
|
|
227
|
-
const newCursortPosition = this.cursor.getBoundingClientRect().left;
|
|
228
|
-
this.scrollContainer.scrollLeft += newCursortPosition - oldCursorPosition;
|
|
229
224
|
}
|
|
230
225
|
renderProgress(progress, autoCenter = false) {
|
|
231
|
-
this.
|
|
232
|
-
this.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
: '';
|
|
239
|
-
this.cursor.scrollIntoView();
|
|
240
|
-
if (autoCenter) {
|
|
241
|
-
const center = this.container.clientWidth / 2;
|
|
242
|
-
const fullWidth = this.mainCanvas.clientWidth;
|
|
243
|
-
if (fullWidth * progress >= center) {
|
|
244
|
-
this.scrollContainer.scrollLeft = fullWidth * progress - center;
|
|
245
|
-
}
|
|
226
|
+
this.progressWrapper.style.width = `${progress * 100}%`;
|
|
227
|
+
const containerWidth = this.scrollContainer.clientWidth;
|
|
228
|
+
const center = containerWidth / 2;
|
|
229
|
+
const progressWidth = this.progressWrapper.clientWidth;
|
|
230
|
+
const minScroll = autoCenter ? center : containerWidth;
|
|
231
|
+
if (progressWidth > this.scrollContainer.scrollLeft + minScroll) {
|
|
232
|
+
this.scrollContainer.scrollLeft = progressWidth - center;
|
|
246
233
|
}
|
|
247
234
|
}
|
|
248
235
|
}
|
package/dist/timer.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import EventEmitter from './event-emitter.js';
|
|
2
2
|
class Timer extends EventEmitter {
|
|
3
|
-
|
|
4
|
-
super(...arguments);
|
|
5
|
-
this.unsubscribe = () => undefined;
|
|
6
|
-
}
|
|
3
|
+
unsubscribe = () => undefined;
|
|
7
4
|
start() {
|
|
8
5
|
this.unsubscribe = this.on('tick', () => {
|
|
9
6
|
requestAnimationFrame(() => {
|