wavesurfer.js 7.0.0-beta.6 → 7.0.0-beta.8
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 +2 -2
- package/dist/plugins/minimap.min.js +1 -1
- package/dist/plugins/regions.d.ts +1 -0
- package/dist/plugins/regions.js +3 -0
- package/dist/plugins/regions.min.js +1 -1
- package/dist/renderer.d.ts +2 -0
- package/dist/renderer.js +33 -19
- package/dist/wavesurfer.d.ts +9 -9
- package/dist/wavesurfer.js +3 -4
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +5 -5
- package/dist/cypress/support/commands.d.ts +0 -1
- package/dist/cypress/support/commands.js +0 -39
- package/dist/cypress/support/e2e.d.ts +0 -1
- package/dist/cypress/support/e2e.js +0 -18
- package/dist/cypress.config.d.ts +0 -2
- package/dist/cypress.config.js +0 -10
- package/dist/plugins/envelope.min.cjs +0 -1
- package/dist/plugins/minimap.min.cjs +0 -1
- package/dist/plugins/multitrack.d.ts +0 -117
- package/dist/plugins/multitrack.js +0 -507
- package/dist/plugins/multitrack.min.cjs +0 -1
- package/dist/plugins/multitrack.min.js +0 -1
- package/dist/plugins/record.min.cjs +0 -1
- package/dist/plugins/regions.min.cjs +0 -1
- package/dist/plugins/spectrogram.min.cjs +0 -1
- package/dist/plugins/timeline.min.cjs +0 -1
- package/dist/src/base-plugin.d.ts +0 -13
- package/dist/src/base-plugin.js +0 -22
- package/dist/src/decoder.d.ts +0 -9
- package/dist/src/decoder.js +0 -48
- package/dist/src/event-emitter.d.ts +0 -19
- package/dist/src/event-emitter.js +0 -45
- package/dist/src/fetcher.d.ts +0 -5
- package/dist/src/fetcher.js +0 -7
- package/dist/src/player.d.ts +0 -45
- package/dist/src/player.js +0 -114
- package/dist/src/plugins/envelope.d.ts +0 -71
- package/dist/src/plugins/envelope.js +0 -350
- package/dist/src/plugins/minimap.d.ts +0 -39
- package/dist/src/plugins/minimap.js +0 -117
- package/dist/src/plugins/record.d.ts +0 -26
- package/dist/src/plugins/record.js +0 -124
- package/dist/src/plugins/regions.d.ts +0 -93
- package/dist/src/plugins/regions.js +0 -395
- package/dist/src/plugins/spectrogram-fft.d.ts +0 -9
- package/dist/src/plugins/spectrogram-fft.js +0 -150
- package/dist/src/plugins/spectrogram.d.ts +0 -69
- package/dist/src/plugins/spectrogram.js +0 -340
- package/dist/src/plugins/timeline.d.ts +0 -45
- package/dist/src/plugins/timeline.js +0 -162
- package/dist/src/renderer.d.ts +0 -41
- package/dist/src/renderer.js +0 -420
- package/dist/src/timer.d.ts +0 -11
- package/dist/src/timer.js +0 -19
- package/dist/src/wavesurfer.d.ts +0 -149
- package/dist/src/wavesurfer.js +0 -229
- package/dist/wavesurfer.min.cjs +0 -1
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Timeline plugin adds timestamps and notches under the waveform.
|
|
3
|
-
*/
|
|
4
|
-
import BasePlugin from '../base-plugin.js';
|
|
5
|
-
const defaultOptions = {
|
|
6
|
-
height: 20,
|
|
7
|
-
};
|
|
8
|
-
class TimelinePlugin extends BasePlugin {
|
|
9
|
-
timelineWrapper;
|
|
10
|
-
options;
|
|
11
|
-
constructor(options) {
|
|
12
|
-
super(options || {});
|
|
13
|
-
this.options = Object.assign({}, defaultOptions, options);
|
|
14
|
-
this.timelineWrapper = this.initTimelineWrapper();
|
|
15
|
-
}
|
|
16
|
-
static create(options) {
|
|
17
|
-
return new TimelinePlugin(options);
|
|
18
|
-
}
|
|
19
|
-
/** Called by wavesurfer, don't call manually */
|
|
20
|
-
onInit() {
|
|
21
|
-
if (!this.wavesurfer) {
|
|
22
|
-
throw Error('WaveSurfer is not initialized');
|
|
23
|
-
}
|
|
24
|
-
const container = this.options.container ?? this.wavesurfer.getWrapper();
|
|
25
|
-
if (this.options.insertPosition) {
|
|
26
|
-
;
|
|
27
|
-
(container.firstElementChild || container).insertAdjacentElement(this.options.insertPosition, this.timelineWrapper);
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
container.appendChild(this.timelineWrapper);
|
|
31
|
-
}
|
|
32
|
-
if (this.options.duration) {
|
|
33
|
-
this.initTimeline(this.options.duration);
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
this.subscriptions.push(this.wavesurfer.on('redraw', () => {
|
|
37
|
-
this.initTimeline(this.wavesurfer?.getDuration() || 0);
|
|
38
|
-
}));
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
/** Unmount */
|
|
42
|
-
destroy() {
|
|
43
|
-
this.timelineWrapper.remove();
|
|
44
|
-
super.destroy();
|
|
45
|
-
}
|
|
46
|
-
initTimelineWrapper() {
|
|
47
|
-
const div = document.createElement('div');
|
|
48
|
-
div.setAttribute('part', 'timeline');
|
|
49
|
-
return div;
|
|
50
|
-
}
|
|
51
|
-
formatTime(seconds) {
|
|
52
|
-
if (seconds / 60 > 1) {
|
|
53
|
-
// calculate minutes and seconds from seconds count
|
|
54
|
-
const minutes = Math.floor(seconds / 60);
|
|
55
|
-
seconds = Math.round(seconds % 60);
|
|
56
|
-
const paddedSeconds = `${seconds < 10 ? '0' : ''}${seconds}`;
|
|
57
|
-
return `${minutes}:${paddedSeconds}`;
|
|
58
|
-
}
|
|
59
|
-
const rounded = Math.round(seconds * 1000) / 1000;
|
|
60
|
-
return `${rounded}`;
|
|
61
|
-
}
|
|
62
|
-
// Return how many seconds should be between each notch
|
|
63
|
-
defaultTimeInterval(pxPerSec) {
|
|
64
|
-
if (pxPerSec >= 25) {
|
|
65
|
-
return 1;
|
|
66
|
-
}
|
|
67
|
-
else if (pxPerSec * 5 >= 25) {
|
|
68
|
-
return 5;
|
|
69
|
-
}
|
|
70
|
-
else if (pxPerSec * 15 >= 25) {
|
|
71
|
-
return 15;
|
|
72
|
-
}
|
|
73
|
-
return Math.ceil(0.5 / pxPerSec) * 60;
|
|
74
|
-
}
|
|
75
|
-
// Return the cadence of notches that get labels in the primary color.
|
|
76
|
-
defaultPrimaryLabelInterval(pxPerSec) {
|
|
77
|
-
if (pxPerSec >= 25) {
|
|
78
|
-
return 10;
|
|
79
|
-
}
|
|
80
|
-
else if (pxPerSec * 5 >= 25) {
|
|
81
|
-
return 6;
|
|
82
|
-
}
|
|
83
|
-
else if (pxPerSec * 15 >= 25) {
|
|
84
|
-
return 4;
|
|
85
|
-
}
|
|
86
|
-
return 4;
|
|
87
|
-
}
|
|
88
|
-
// Return the cadence of notches that get labels in the secondary color.
|
|
89
|
-
defaultSecondaryLabelInterval(pxPerSec) {
|
|
90
|
-
if (pxPerSec >= 25) {
|
|
91
|
-
return 5;
|
|
92
|
-
}
|
|
93
|
-
else if (pxPerSec * 5 >= 25) {
|
|
94
|
-
return 2;
|
|
95
|
-
}
|
|
96
|
-
else if (pxPerSec * 15 >= 25) {
|
|
97
|
-
return 2;
|
|
98
|
-
}
|
|
99
|
-
return 2;
|
|
100
|
-
}
|
|
101
|
-
initTimeline(duration) {
|
|
102
|
-
const pxPerSec = this.timelineWrapper.scrollWidth / duration;
|
|
103
|
-
const timeInterval = this.options.timeInterval ?? this.defaultTimeInterval(pxPerSec);
|
|
104
|
-
const primaryLabelInterval = this.options.primaryLabelInterval ?? this.defaultPrimaryLabelInterval(pxPerSec);
|
|
105
|
-
const secondaryLabelInterval = this.options.secondaryLabelInterval ?? this.defaultSecondaryLabelInterval(pxPerSec);
|
|
106
|
-
const isTop = this.options.insertPosition === 'beforebegin';
|
|
107
|
-
const timeline = document.createElement('div');
|
|
108
|
-
timeline.setAttribute('style', `
|
|
109
|
-
height: ${this.options.height}px;
|
|
110
|
-
overflow: hidden;
|
|
111
|
-
display: flex;
|
|
112
|
-
justify-content: space-between;
|
|
113
|
-
align-items: ${isTop ? 'flex-start' : 'flex-end'};
|
|
114
|
-
font-size: ${this.options.height / 2}px;
|
|
115
|
-
white-space: nowrap;
|
|
116
|
-
`);
|
|
117
|
-
if (isTop) {
|
|
118
|
-
const topStyle = `
|
|
119
|
-
position: absolute;
|
|
120
|
-
top: 0;
|
|
121
|
-
left: 0;
|
|
122
|
-
right: 0;
|
|
123
|
-
z-index: 2;
|
|
124
|
-
`;
|
|
125
|
-
timeline.setAttribute('style', timeline.getAttribute('style') + topStyle);
|
|
126
|
-
}
|
|
127
|
-
if (typeof this.options.style === 'string') {
|
|
128
|
-
timeline.setAttribute('style', timeline.getAttribute('style') + this.options.style);
|
|
129
|
-
}
|
|
130
|
-
else if (typeof this.options.style === 'object') {
|
|
131
|
-
Object.assign(timeline.style, this.options.style);
|
|
132
|
-
}
|
|
133
|
-
const notchEl = document.createElement('div');
|
|
134
|
-
notchEl.setAttribute('style', `
|
|
135
|
-
width: 1px;
|
|
136
|
-
height: 50%;
|
|
137
|
-
display: flex;
|
|
138
|
-
flex-direction: column;
|
|
139
|
-
justify-content: ${isTop ? 'flex-start' : 'flex-end'};
|
|
140
|
-
overflow: visible;
|
|
141
|
-
border-left: 1px solid currentColor;
|
|
142
|
-
opacity: 0.25;
|
|
143
|
-
`);
|
|
144
|
-
for (let i = 0; i < duration; i += timeInterval) {
|
|
145
|
-
const notch = notchEl.cloneNode();
|
|
146
|
-
const isPrimary = (Math.round(i * 100) / 100) % primaryLabelInterval === 0;
|
|
147
|
-
const isSecondary = (Math.round(i * 100) / 100) % secondaryLabelInterval === 0;
|
|
148
|
-
if (isPrimary || isSecondary) {
|
|
149
|
-
notch.style.height = '100%';
|
|
150
|
-
notch.style.textIndent = '3px';
|
|
151
|
-
notch.textContent = this.formatTime(i);
|
|
152
|
-
if (isPrimary)
|
|
153
|
-
notch.style.opacity = '1';
|
|
154
|
-
}
|
|
155
|
-
timeline.appendChild(notch);
|
|
156
|
-
}
|
|
157
|
-
this.timelineWrapper.innerHTML = '';
|
|
158
|
-
this.timelineWrapper.appendChild(timeline);
|
|
159
|
-
this.emit('ready');
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
export default TimelinePlugin;
|
package/dist/src/renderer.d.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import EventEmitter from './event-emitter.js';
|
|
2
|
-
import type { WaveSurferOptions } from './wavesurfer.js';
|
|
3
|
-
type RendererEvents = {
|
|
4
|
-
click: [relativeX: number];
|
|
5
|
-
drag: [relativeX: number];
|
|
6
|
-
scroll: [relativeStart: number, relativeEnd: number];
|
|
7
|
-
render: [];
|
|
8
|
-
};
|
|
9
|
-
declare class Renderer extends EventEmitter<RendererEvents> {
|
|
10
|
-
private static MAX_CANVAS_WIDTH;
|
|
11
|
-
private options;
|
|
12
|
-
private container;
|
|
13
|
-
private scrollContainer;
|
|
14
|
-
private wrapper;
|
|
15
|
-
private canvasWrapper;
|
|
16
|
-
private progressWrapper;
|
|
17
|
-
private cursor;
|
|
18
|
-
private timeouts;
|
|
19
|
-
private isScrolling;
|
|
20
|
-
private audioData;
|
|
21
|
-
private resizeObserver;
|
|
22
|
-
private isDragging;
|
|
23
|
-
constructor(options: WaveSurferOptions);
|
|
24
|
-
private initEvents;
|
|
25
|
-
private initDrag;
|
|
26
|
-
private initHtml;
|
|
27
|
-
setOptions(options: WaveSurferOptions): void;
|
|
28
|
-
getWrapper(): HTMLElement;
|
|
29
|
-
getScroll(): number;
|
|
30
|
-
destroy(): void;
|
|
31
|
-
private createDelay;
|
|
32
|
-
private convertColorValues;
|
|
33
|
-
private renderSingleCanvas;
|
|
34
|
-
private renderWaveform;
|
|
35
|
-
render(audioData: AudioBuffer): void;
|
|
36
|
-
reRender(): void;
|
|
37
|
-
zoom(minPxPerSec: number): void;
|
|
38
|
-
private scrollIntoView;
|
|
39
|
-
renderProgress(progress: number, isPlaying?: boolean): void;
|
|
40
|
-
}
|
|
41
|
-
export default Renderer;
|
package/dist/src/renderer.js
DELETED
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
import EventEmitter from './event-emitter.js';
|
|
2
|
-
class Renderer extends EventEmitter {
|
|
3
|
-
static MAX_CANVAS_WIDTH = 4000;
|
|
4
|
-
options;
|
|
5
|
-
container;
|
|
6
|
-
scrollContainer;
|
|
7
|
-
wrapper;
|
|
8
|
-
canvasWrapper;
|
|
9
|
-
progressWrapper;
|
|
10
|
-
cursor;
|
|
11
|
-
timeouts = [];
|
|
12
|
-
isScrolling = false;
|
|
13
|
-
audioData = null;
|
|
14
|
-
resizeObserver = null;
|
|
15
|
-
isDragging = false;
|
|
16
|
-
constructor(options) {
|
|
17
|
-
super();
|
|
18
|
-
this.options = options;
|
|
19
|
-
let container;
|
|
20
|
-
if (typeof options.container === 'string') {
|
|
21
|
-
container = document.querySelector(options.container);
|
|
22
|
-
}
|
|
23
|
-
else if (options.container instanceof HTMLElement) {
|
|
24
|
-
container = options.container;
|
|
25
|
-
}
|
|
26
|
-
if (!container) {
|
|
27
|
-
throw new Error('Container not found');
|
|
28
|
-
}
|
|
29
|
-
const [div, shadow] = this.initHtml();
|
|
30
|
-
container.appendChild(div);
|
|
31
|
-
this.container = div;
|
|
32
|
-
this.scrollContainer = shadow.querySelector('.scroll');
|
|
33
|
-
this.wrapper = shadow.querySelector('.wrapper');
|
|
34
|
-
this.canvasWrapper = shadow.querySelector('.canvases');
|
|
35
|
-
this.progressWrapper = shadow.querySelector('.progress');
|
|
36
|
-
this.cursor = shadow.querySelector('.cursor');
|
|
37
|
-
this.initEvents();
|
|
38
|
-
}
|
|
39
|
-
initEvents() {
|
|
40
|
-
// Add a click listener
|
|
41
|
-
this.wrapper.addEventListener('click', (e) => {
|
|
42
|
-
const rect = this.wrapper.getBoundingClientRect();
|
|
43
|
-
const x = e.clientX - rect.left;
|
|
44
|
-
const relativeX = x / rect.width;
|
|
45
|
-
this.emit('click', relativeX);
|
|
46
|
-
});
|
|
47
|
-
// Drag
|
|
48
|
-
this.initDrag();
|
|
49
|
-
// Add a scroll listener
|
|
50
|
-
this.scrollContainer.addEventListener('scroll', () => {
|
|
51
|
-
const { scrollLeft, scrollWidth, clientWidth } = this.scrollContainer;
|
|
52
|
-
const startX = scrollLeft / scrollWidth;
|
|
53
|
-
const endX = (scrollLeft + clientWidth) / scrollWidth;
|
|
54
|
-
this.emit('scroll', startX, endX);
|
|
55
|
-
});
|
|
56
|
-
// Re-render the waveform on container resize
|
|
57
|
-
const delay = this.createDelay(100);
|
|
58
|
-
this.resizeObserver = new ResizeObserver(() => {
|
|
59
|
-
delay(() => this.reRender());
|
|
60
|
-
});
|
|
61
|
-
this.resizeObserver.observe(this.scrollContainer);
|
|
62
|
-
}
|
|
63
|
-
initDrag() {
|
|
64
|
-
this.wrapper.addEventListener('mousedown', (e) => {
|
|
65
|
-
const minDx = 5;
|
|
66
|
-
const x = e.clientX;
|
|
67
|
-
const move = (e) => {
|
|
68
|
-
const diff = Math.abs(e.clientX - x);
|
|
69
|
-
if (diff >= minDx) {
|
|
70
|
-
this.isDragging = true;
|
|
71
|
-
const rect = this.wrapper.getBoundingClientRect();
|
|
72
|
-
this.emit('drag', Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)));
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
const up = () => {
|
|
76
|
-
document.removeEventListener('mousemove', move);
|
|
77
|
-
document.removeEventListener('mouseup', up);
|
|
78
|
-
this.isDragging = false;
|
|
79
|
-
};
|
|
80
|
-
document.addEventListener('mousemove', move);
|
|
81
|
-
document.addEventListener('mouseup', up);
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
initHtml() {
|
|
85
|
-
const div = document.createElement('div');
|
|
86
|
-
const shadow = div.attachShadow({ mode: 'open' });
|
|
87
|
-
shadow.innerHTML = `
|
|
88
|
-
<style>
|
|
89
|
-
:host {
|
|
90
|
-
user-select: none;
|
|
91
|
-
}
|
|
92
|
-
:host .scroll {
|
|
93
|
-
overflow-x: auto;
|
|
94
|
-
overflow-y: hidden;
|
|
95
|
-
width: 100%;
|
|
96
|
-
position: relative;
|
|
97
|
-
}
|
|
98
|
-
:host .noScrollbar {
|
|
99
|
-
scrollbar-color: transparent;
|
|
100
|
-
scrollbar-width: none;
|
|
101
|
-
}
|
|
102
|
-
:host .noScrollbar::-webkit-scrollbar {
|
|
103
|
-
display: none;
|
|
104
|
-
-webkit-appearance: none;
|
|
105
|
-
}
|
|
106
|
-
:host .wrapper {
|
|
107
|
-
position: relative;
|
|
108
|
-
overflow: visible;
|
|
109
|
-
z-index: 2;
|
|
110
|
-
}
|
|
111
|
-
:host .canvases {
|
|
112
|
-
min-height: ${this.options.height}px;
|
|
113
|
-
}
|
|
114
|
-
:host .canvases > div {
|
|
115
|
-
position: relative;
|
|
116
|
-
}
|
|
117
|
-
:host canvas {
|
|
118
|
-
display: block;
|
|
119
|
-
position: absolute;
|
|
120
|
-
top: 0;
|
|
121
|
-
image-rendering: crisp-edges;
|
|
122
|
-
}
|
|
123
|
-
:host .progress {
|
|
124
|
-
pointer-events: none;
|
|
125
|
-
position: absolute;
|
|
126
|
-
z-index: 2;
|
|
127
|
-
top: 0;
|
|
128
|
-
left: 0;
|
|
129
|
-
width: 0;
|
|
130
|
-
height: 100%;
|
|
131
|
-
overflow: hidden;
|
|
132
|
-
}
|
|
133
|
-
:host .progress > div {
|
|
134
|
-
position: relative;
|
|
135
|
-
}
|
|
136
|
-
:host .cursor {
|
|
137
|
-
pointer-events: none;
|
|
138
|
-
position: absolute;
|
|
139
|
-
z-index: 5;
|
|
140
|
-
top: 0;
|
|
141
|
-
left: 0;
|
|
142
|
-
height: 100%;
|
|
143
|
-
border-radius: 2px;
|
|
144
|
-
}
|
|
145
|
-
</style>
|
|
146
|
-
|
|
147
|
-
<div class="scroll" part="scroll">
|
|
148
|
-
<div class="wrapper">
|
|
149
|
-
<div class="canvases"></div>
|
|
150
|
-
<div class="progress"></div>
|
|
151
|
-
<div class="cursor" part="cursor"></div>
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
`;
|
|
155
|
-
return [div, shadow];
|
|
156
|
-
}
|
|
157
|
-
setOptions(options) {
|
|
158
|
-
this.options = options;
|
|
159
|
-
// Re-render the waveform
|
|
160
|
-
this.reRender();
|
|
161
|
-
}
|
|
162
|
-
getWrapper() {
|
|
163
|
-
return this.wrapper;
|
|
164
|
-
}
|
|
165
|
-
getScroll() {
|
|
166
|
-
return this.scrollContainer.scrollLeft;
|
|
167
|
-
}
|
|
168
|
-
destroy() {
|
|
169
|
-
this.container.remove();
|
|
170
|
-
this.resizeObserver?.disconnect();
|
|
171
|
-
}
|
|
172
|
-
createDelay(delayMs = 10) {
|
|
173
|
-
const context = {};
|
|
174
|
-
this.timeouts.push(context);
|
|
175
|
-
return (callback) => {
|
|
176
|
-
context.timeout && clearTimeout(context.timeout);
|
|
177
|
-
context.timeout = setTimeout(callback, delayMs);
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
// Convert array of color values to linear gradient
|
|
181
|
-
convertColorValues(color) {
|
|
182
|
-
if (!Array.isArray(color))
|
|
183
|
-
return color || '';
|
|
184
|
-
if (color.length < 2)
|
|
185
|
-
return color.length === 1 ? color[0] : '';
|
|
186
|
-
const canvasElement = document.createElement('canvas');
|
|
187
|
-
const ctx = canvasElement.getContext('2d');
|
|
188
|
-
const gradient = ctx.createLinearGradient(0, 0, 0, canvasElement.height);
|
|
189
|
-
const colorStopPercentage = 1 / (color.length - 1);
|
|
190
|
-
color.forEach((color, index) => {
|
|
191
|
-
const offset = index * colorStopPercentage;
|
|
192
|
-
gradient.addColorStop(offset, color);
|
|
193
|
-
});
|
|
194
|
-
return gradient;
|
|
195
|
-
}
|
|
196
|
-
renderSingleCanvas(channelData, options, width, start, end, canvasContainer, progressContainer) {
|
|
197
|
-
const pixelRatio = window.devicePixelRatio || 1;
|
|
198
|
-
const height = options.height || 0;
|
|
199
|
-
const barWidth = options.barWidth != null && !isNaN(options.barWidth) ? options.barWidth * pixelRatio : 1;
|
|
200
|
-
const barGap = options.barGap != null && !isNaN(options.barGap)
|
|
201
|
-
? options.barGap * pixelRatio
|
|
202
|
-
: options.barWidth
|
|
203
|
-
? barWidth / 2
|
|
204
|
-
: 0;
|
|
205
|
-
const barRadius = options.barRadius || 0;
|
|
206
|
-
const barScale = options.barHeight || 1;
|
|
207
|
-
const isMono = channelData.length === 1;
|
|
208
|
-
const leftChannel = channelData[0];
|
|
209
|
-
const rightChannel = isMono ? leftChannel : channelData[1];
|
|
210
|
-
const useNegative = isMono && rightChannel.some((v) => v < 0);
|
|
211
|
-
const length = leftChannel.length;
|
|
212
|
-
const barCount = Math.floor(width / (barWidth + barGap));
|
|
213
|
-
const barIndexScale = barCount / length;
|
|
214
|
-
const halfHeight = height / 2;
|
|
215
|
-
let prevX = 0;
|
|
216
|
-
let prevLeft = 0;
|
|
217
|
-
let prevRight = 0;
|
|
218
|
-
const canvas = document.createElement('canvas');
|
|
219
|
-
canvas.width = Math.round((width * (end - start)) / length);
|
|
220
|
-
canvas.height = height * pixelRatio;
|
|
221
|
-
canvas.style.width = `${Math.floor(canvas.width / pixelRatio)}px`;
|
|
222
|
-
canvas.style.height = `${options.height}px`;
|
|
223
|
-
canvas.style.left = `${Math.floor((start * width) / pixelRatio / length)}px`;
|
|
224
|
-
canvasContainer.appendChild(canvas);
|
|
225
|
-
const ctx = canvas.getContext('2d', {
|
|
226
|
-
desynchronized: true,
|
|
227
|
-
});
|
|
228
|
-
ctx.beginPath();
|
|
229
|
-
ctx.fillStyle = this.convertColorValues(options.waveColor);
|
|
230
|
-
// Firefox shim until 2023.04.11
|
|
231
|
-
if (!ctx.roundRect)
|
|
232
|
-
ctx.roundRect = ctx.fillRect;
|
|
233
|
-
for (let i = start; i < end; i++) {
|
|
234
|
-
const barIndex = Math.round((i - start) * barIndexScale);
|
|
235
|
-
if (barIndex > prevX) {
|
|
236
|
-
const leftBarHeight = Math.round(prevLeft * halfHeight * barScale);
|
|
237
|
-
const rightBarHeight = Math.round(prevRight * halfHeight * barScale);
|
|
238
|
-
ctx.roundRect(prevX * (barWidth + barGap), halfHeight - leftBarHeight, barWidth, leftBarHeight + (rightBarHeight || 1), barRadius);
|
|
239
|
-
prevX = barIndex;
|
|
240
|
-
prevLeft = 0;
|
|
241
|
-
prevRight = 0;
|
|
242
|
-
}
|
|
243
|
-
const leftValue = useNegative ? leftChannel[i] : Math.abs(leftChannel[i]);
|
|
244
|
-
const rightValue = useNegative ? rightChannel[i] : Math.abs(rightChannel[i]);
|
|
245
|
-
if (leftValue > prevLeft) {
|
|
246
|
-
prevLeft = leftValue;
|
|
247
|
-
}
|
|
248
|
-
// If stereo, both channels are drawn as max values
|
|
249
|
-
// If mono with negative values, the bottom channel will be the min negative values
|
|
250
|
-
if (useNegative ? rightValue < -prevRight : rightValue > prevRight) {
|
|
251
|
-
prevRight = rightValue < 0 ? -rightValue : rightValue;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
ctx.fill();
|
|
255
|
-
ctx.closePath();
|
|
256
|
-
// Draw a progress canvas
|
|
257
|
-
const progressCanvas = canvas.cloneNode();
|
|
258
|
-
progressContainer.appendChild(progressCanvas);
|
|
259
|
-
const progressCtx = progressCanvas.getContext('2d', {
|
|
260
|
-
desynchronized: true,
|
|
261
|
-
});
|
|
262
|
-
if (canvas.width > 0 && canvas.height > 0) {
|
|
263
|
-
progressCtx.drawImage(canvas, 0, 0);
|
|
264
|
-
}
|
|
265
|
-
// Set the composition method to draw only where the waveform is drawn
|
|
266
|
-
progressCtx.globalCompositeOperation = 'source-in';
|
|
267
|
-
progressCtx.fillStyle = this.convertColorValues(options.progressColor);
|
|
268
|
-
// This rectangle acts as a mask thanks to the composition method
|
|
269
|
-
progressCtx.fillRect(0, 0, canvas.width, canvas.height);
|
|
270
|
-
}
|
|
271
|
-
renderWaveform(channelData, options, width) {
|
|
272
|
-
// A container for canvases
|
|
273
|
-
const canvasContainer = document.createElement('div');
|
|
274
|
-
canvasContainer.style.height = `${options.height}px`;
|
|
275
|
-
this.canvasWrapper.appendChild(canvasContainer);
|
|
276
|
-
// A container for progress canvases
|
|
277
|
-
const progressContainer = canvasContainer.cloneNode();
|
|
278
|
-
this.progressWrapper.appendChild(progressContainer);
|
|
279
|
-
// Determine the currently visible part of the waveform
|
|
280
|
-
const { scrollLeft, scrollWidth, clientWidth } = this.scrollContainer;
|
|
281
|
-
const len = channelData[0].length;
|
|
282
|
-
const scale = len / scrollWidth;
|
|
283
|
-
const viewportWidth = Math.min(Renderer.MAX_CANVAS_WIDTH, clientWidth);
|
|
284
|
-
const start = Math.floor(Math.abs(scrollLeft) * scale);
|
|
285
|
-
const end = Math.ceil(start + viewportWidth * scale);
|
|
286
|
-
const viewportLen = end - start;
|
|
287
|
-
// Draw a portion of the waveform from start peak to end peak
|
|
288
|
-
const draw = (start, end) => {
|
|
289
|
-
this.renderSingleCanvas(channelData, options, width, Math.max(0, start), Math.min(end, len), canvasContainer, progressContainer);
|
|
290
|
-
};
|
|
291
|
-
// Draw the waveform in viewport chunks, each with a delay
|
|
292
|
-
const headDelay = this.createDelay();
|
|
293
|
-
const tailDelay = this.createDelay();
|
|
294
|
-
const renderHead = (fromIndex, toIndex) => {
|
|
295
|
-
draw(fromIndex, toIndex);
|
|
296
|
-
if (fromIndex > 0) {
|
|
297
|
-
headDelay(() => {
|
|
298
|
-
renderHead(fromIndex - viewportLen, toIndex - viewportLen);
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
const renderTail = (fromIndex, toIndex) => {
|
|
303
|
-
draw(fromIndex, toIndex);
|
|
304
|
-
if (toIndex < len) {
|
|
305
|
-
tailDelay(() => {
|
|
306
|
-
renderTail(fromIndex + viewportLen, toIndex + viewportLen);
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
};
|
|
310
|
-
renderHead(start, end);
|
|
311
|
-
if (end < len) {
|
|
312
|
-
renderTail(end, end + viewportLen);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
render(audioData) {
|
|
316
|
-
// Clear previous timeouts
|
|
317
|
-
this.timeouts.forEach((context) => context.timeout && clearTimeout(context.timeout));
|
|
318
|
-
this.timeouts = [];
|
|
319
|
-
// Determine the width of the waveform
|
|
320
|
-
const pixelRatio = window.devicePixelRatio || 1;
|
|
321
|
-
const parentWidth = this.scrollContainer.clientWidth;
|
|
322
|
-
const scrollWidth = Math.ceil(audioData.duration * (this.options.minPxPerSec || 0));
|
|
323
|
-
// Whether the container should scroll
|
|
324
|
-
this.isScrolling = scrollWidth > parentWidth;
|
|
325
|
-
const useParentWidth = this.options.fillParent && !this.isScrolling;
|
|
326
|
-
// Width and height of the waveform in pixels
|
|
327
|
-
const width = (useParentWidth ? parentWidth : scrollWidth) * pixelRatio;
|
|
328
|
-
// Set the width of the wrapper
|
|
329
|
-
this.wrapper.style.width = useParentWidth ? '100%' : `${scrollWidth}px`;
|
|
330
|
-
// Set additional styles
|
|
331
|
-
this.scrollContainer.style.overflowX = this.isScrolling ? 'auto' : 'hidden';
|
|
332
|
-
this.scrollContainer.classList.toggle('noScrollbar', !!this.options.hideScrollbar);
|
|
333
|
-
this.cursor.style.backgroundColor = `${this.options.cursorColor || this.options.progressColor}`;
|
|
334
|
-
this.cursor.style.width = `${this.options.cursorWidth}px`;
|
|
335
|
-
// Clear the canvases
|
|
336
|
-
this.canvasWrapper.innerHTML = '';
|
|
337
|
-
this.progressWrapper.innerHTML = '';
|
|
338
|
-
// Render the waveform
|
|
339
|
-
if (this.options.splitChannels) {
|
|
340
|
-
// Render a waveform for each channel
|
|
341
|
-
for (let i = 0; i < audioData.numberOfChannels; i++) {
|
|
342
|
-
const options = { ...this.options, ...this.options.splitChannels[i] };
|
|
343
|
-
this.renderWaveform([audioData.getChannelData(i)], options, width);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
else {
|
|
347
|
-
// Render a single waveform for the first two channels (left and right)
|
|
348
|
-
const channels = [audioData.getChannelData(0)];
|
|
349
|
-
if (audioData.numberOfChannels > 1)
|
|
350
|
-
channels.push(audioData.getChannelData(1));
|
|
351
|
-
this.renderWaveform(channels, this.options, width);
|
|
352
|
-
}
|
|
353
|
-
this.audioData = audioData;
|
|
354
|
-
this.emit('render');
|
|
355
|
-
}
|
|
356
|
-
reRender() {
|
|
357
|
-
// Return if the waveform has not been rendered yet
|
|
358
|
-
if (!this.audioData)
|
|
359
|
-
return;
|
|
360
|
-
// Remember the current cursor position
|
|
361
|
-
const oldCursorPosition = this.progressWrapper.clientWidth;
|
|
362
|
-
// Set the new zoom level and re-render the waveform
|
|
363
|
-
this.render(this.audioData);
|
|
364
|
-
// Adjust the scroll position so that the cursor stays in the same place
|
|
365
|
-
const newCursortPosition = this.progressWrapper.clientWidth;
|
|
366
|
-
this.scrollContainer.scrollLeft += newCursortPosition - oldCursorPosition;
|
|
367
|
-
}
|
|
368
|
-
zoom(minPxPerSec) {
|
|
369
|
-
this.options.minPxPerSec = minPxPerSec;
|
|
370
|
-
this.reRender();
|
|
371
|
-
}
|
|
372
|
-
scrollIntoView(progress, isPlaying = false) {
|
|
373
|
-
const { clientWidth, scrollLeft, scrollWidth } = this.scrollContainer;
|
|
374
|
-
const progressWidth = scrollWidth * progress;
|
|
375
|
-
const center = clientWidth / 2;
|
|
376
|
-
const minScroll = isPlaying && this.options.autoCenter && !this.isDragging ? center : clientWidth;
|
|
377
|
-
if (progressWidth > scrollLeft + minScroll || progressWidth < scrollLeft) {
|
|
378
|
-
// Scroll to the center
|
|
379
|
-
if (this.options.autoCenter && !this.isDragging) {
|
|
380
|
-
// If the cursor is in viewport but not centered, scroll to the center slowly
|
|
381
|
-
const minDiff = center / 20;
|
|
382
|
-
if (progressWidth - (scrollLeft + center) >= minDiff && progressWidth < scrollLeft + clientWidth) {
|
|
383
|
-
this.scrollContainer.scrollLeft += minDiff;
|
|
384
|
-
}
|
|
385
|
-
else {
|
|
386
|
-
// Otherwise, scroll to the center immediately
|
|
387
|
-
this.scrollContainer.scrollLeft = progressWidth - center;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
else if (this.isDragging) {
|
|
391
|
-
// Scroll just a little bit to allow for some space between the cursor and the edge
|
|
392
|
-
const gap = 10;
|
|
393
|
-
this.scrollContainer.scrollLeft =
|
|
394
|
-
progressWidth < scrollLeft ? progressWidth - gap : progressWidth - clientWidth + gap;
|
|
395
|
-
}
|
|
396
|
-
else {
|
|
397
|
-
// Scroll to the beginning
|
|
398
|
-
this.scrollContainer.scrollLeft = progressWidth;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
// Emit the scroll event
|
|
402
|
-
{
|
|
403
|
-
const { scrollLeft } = this.scrollContainer;
|
|
404
|
-
const startX = scrollLeft / scrollWidth;
|
|
405
|
-
const endX = (scrollLeft + clientWidth) / scrollWidth;
|
|
406
|
-
this.emit('scroll', startX, endX);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
renderProgress(progress, isPlaying) {
|
|
410
|
-
if (isNaN(progress))
|
|
411
|
-
return;
|
|
412
|
-
this.progressWrapper.style.width = `${progress * 100}%`;
|
|
413
|
-
this.cursor.style.left = `${progress * 100}%`;
|
|
414
|
-
this.cursor.style.marginLeft = Math.round(progress * 100) === 100 ? `-${this.options.cursorWidth}px` : '';
|
|
415
|
-
if (this.isScrolling && this.options.autoScroll) {
|
|
416
|
-
this.scrollIntoView(progress, isPlaying);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
export default Renderer;
|
package/dist/src/timer.d.ts
DELETED
package/dist/src/timer.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import EventEmitter from './event-emitter.js';
|
|
2
|
-
class Timer extends EventEmitter {
|
|
3
|
-
unsubscribe = () => undefined;
|
|
4
|
-
start() {
|
|
5
|
-
this.unsubscribe = this.on('tick', () => {
|
|
6
|
-
requestAnimationFrame(() => {
|
|
7
|
-
this.emit('tick');
|
|
8
|
-
});
|
|
9
|
-
});
|
|
10
|
-
this.emit('tick');
|
|
11
|
-
}
|
|
12
|
-
stop() {
|
|
13
|
-
this.unsubscribe();
|
|
14
|
-
}
|
|
15
|
-
destroy() {
|
|
16
|
-
this.unsubscribe();
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
export default Timer;
|