wavesurfer.js 6.5.1 → 7.0.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/.eslintrc.json +24 -0
- package/.prettierrc +8 -0
- package/README.md +26 -117
- package/dist/base-plugin.d.ts +10 -0
- package/dist/base-plugin.js +13 -0
- package/dist/decoder.d.ts +10 -0
- package/dist/decoder.js +43 -0
- package/dist/event-emitter.d.ts +11 -0
- package/dist/event-emitter.js +21 -0
- package/dist/fetcher.d.ts +4 -0
- package/dist/fetcher.js +17 -0
- package/dist/index.d.ts +92 -0
- package/dist/index.js +166 -0
- package/dist/player-webaudio.d.ts +8 -0
- package/dist/player-webaudio.js +31 -0
- package/dist/player.d.ts +16 -0
- package/dist/player.js +40 -0
- package/dist/plugins/regions.d.ts +48 -0
- package/dist/plugins/regions.js +183 -0
- package/dist/renderer.d.ts +34 -0
- package/dist/renderer.js +193 -0
- package/dist/timer.d.ts +10 -0
- package/dist/timer.js +17 -0
- package/examples/audio.ogg +0 -0
- package/examples/bars.js +19 -0
- package/examples/basic.js +8 -0
- package/examples/gradient.js +28 -0
- package/examples/regions.js +63 -0
- package/examples/video.js +19 -0
- package/examples/webaudio.js +14 -0
- package/package.json +19 -99
- package/src/base-plugin.ts +20 -0
- package/src/decoder.ts +41 -0
- package/src/event-emitter.ts +35 -0
- package/src/fetcher.ts +7 -0
- package/src/index.ts +252 -0
- package/src/player-webaudio.ts +34 -0
- package/src/player.ts +50 -0
- package/src/plugins/regions.ts +240 -0
- package/src/renderer.ts +250 -0
- package/src/timer.ts +27 -0
- package/tsconfig.json +105 -0
- package/tutorial/index.html +47 -0
- package/tutorial/src/editor.js +70 -0
- package/tutorial/src/init.js +66 -0
- package/tutorial/src/url.js +25 -0
- package/tutorial/style.css +211 -0
- package/yarn-error.log +1049 -0
- package/LICENSE +0 -29
- package/dist/plugin/wavesurfer.cursor.js +0 -418
- package/dist/plugin/wavesurfer.cursor.js.map +0 -1
- package/dist/plugin/wavesurfer.cursor.min.js +0 -7
- package/dist/plugin/wavesurfer.cursor.min.js.map +0 -1
- package/dist/plugin/wavesurfer.elan.js +0 -381
- package/dist/plugin/wavesurfer.elan.js.map +0 -1
- package/dist/plugin/wavesurfer.elan.min.js +0 -7
- package/dist/plugin/wavesurfer.elan.min.js.map +0 -1
- package/dist/plugin/wavesurfer.markers.js +0 -520
- package/dist/plugin/wavesurfer.markers.js.map +0 -1
- package/dist/plugin/wavesurfer.markers.min.js +0 -7
- package/dist/plugin/wavesurfer.markers.min.js.map +0 -1
- package/dist/plugin/wavesurfer.mediasession.js +0 -181
- package/dist/plugin/wavesurfer.mediasession.js.map +0 -1
- package/dist/plugin/wavesurfer.mediasession.min.js +0 -7
- package/dist/plugin/wavesurfer.mediasession.min.js.map +0 -1
- package/dist/plugin/wavesurfer.microphone.js +0 -466
- package/dist/plugin/wavesurfer.microphone.js.map +0 -1
- package/dist/plugin/wavesurfer.microphone.min.js +0 -7
- package/dist/plugin/wavesurfer.microphone.min.js.map +0 -1
- package/dist/plugin/wavesurfer.minimap.js +0 -440
- package/dist/plugin/wavesurfer.minimap.js.map +0 -1
- package/dist/plugin/wavesurfer.minimap.min.js +0 -7
- package/dist/plugin/wavesurfer.minimap.min.js.map +0 -1
- package/dist/plugin/wavesurfer.playhead.js +0 -300
- package/dist/plugin/wavesurfer.playhead.js.map +0 -1
- package/dist/plugin/wavesurfer.playhead.min.js +0 -7
- package/dist/plugin/wavesurfer.playhead.min.js.map +0 -1
- package/dist/plugin/wavesurfer.regions.js +0 -1335
- package/dist/plugin/wavesurfer.regions.js.map +0 -1
- package/dist/plugin/wavesurfer.regions.min.js +0 -7
- package/dist/plugin/wavesurfer.regions.min.js.map +0 -1
- package/dist/plugin/wavesurfer.spectrogram.js +0 -736
- package/dist/plugin/wavesurfer.spectrogram.js.map +0 -1
- package/dist/plugin/wavesurfer.spectrogram.min.js +0 -7
- package/dist/plugin/wavesurfer.spectrogram.min.js.map +0 -1
- package/dist/plugin/wavesurfer.timeline.js +0 -649
- package/dist/plugin/wavesurfer.timeline.js.map +0 -1
- package/dist/plugin/wavesurfer.timeline.min.js +0 -7
- package/dist/plugin/wavesurfer.timeline.min.js.map +0 -1
- package/dist/wavesurfer-html-init.js +0 -381
- package/dist/wavesurfer-html-init.js.map +0 -1
- package/dist/wavesurfer-html-init.min.js +0 -7
- package/dist/wavesurfer-html-init.min.js.map +0 -1
- package/dist/wavesurfer.js +0 -6145
- package/dist/wavesurfer.js.map +0 -1
- package/dist/wavesurfer.min.js +0 -7
- package/dist/wavesurfer.min.js.map +0 -1
- package/src/drawer.canvasentry.js +0 -427
- package/src/drawer.js +0 -413
- package/src/drawer.multicanvas.js +0 -617
- package/src/html-init.js +0 -241
- package/src/mediaelement-webaudio.js +0 -77
- package/src/mediaelement.js +0 -443
- package/src/peakcache.js +0 -127
- package/src/plugin/cursor/index.js +0 -350
- package/src/plugin/elan/index.js +0 -307
- package/src/plugin/markers/index.js +0 -436
- package/src/plugin/mediasession/index.js +0 -94
- package/src/plugin/microphone/index.js +0 -415
- package/src/plugin/minimap/index.js +0 -403
- package/src/plugin/playhead/index.js +0 -226
- package/src/plugin/regions/index.js +0 -478
- package/src/plugin/regions/region.js +0 -875
- package/src/plugin/spectrogram/fft.js +0 -222
- package/src/plugin/spectrogram/index.js +0 -522
- package/src/plugin/timeline/index.js +0 -600
- package/src/util/absMax.js +0 -16
- package/src/util/clamp.js +0 -11
- package/src/util/fetch.js +0 -222
- package/src/util/frame.js +0 -13
- package/src/util/get-id.js +0 -22
- package/src/util/index.js +0 -14
- package/src/util/max.js +0 -16
- package/src/util/min.js +0 -16
- package/src/util/observer.js +0 -144
- package/src/util/orientation.js +0 -98
- package/src/util/prevent-click.js +0 -19
- package/src/util/request-animation-frame.js +0 -15
- package/src/util/silence-mode.js +0 -42
- package/src/util/style.js +0 -16
- package/src/wavesurfer.js +0 -1797
- package/src/webaudio.js +0 -779
package/dist/player.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
class Player {
|
|
2
|
+
constructor({ media }) {
|
|
3
|
+
this.isExternalMedia = false;
|
|
4
|
+
if (media) {
|
|
5
|
+
this.media = media;
|
|
6
|
+
this.isExternalMedia = true;
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
this.media = document.createElement('audio');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
on(event, callback) {
|
|
13
|
+
this.media.addEventListener(event, callback);
|
|
14
|
+
return () => this.media.removeEventListener(event, callback);
|
|
15
|
+
}
|
|
16
|
+
destroy() {
|
|
17
|
+
if (!this.isExternalMedia) {
|
|
18
|
+
this.media.remove();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
loadUrl(src) {
|
|
22
|
+
this.media.src = src;
|
|
23
|
+
}
|
|
24
|
+
getCurrentTime() {
|
|
25
|
+
return this.media.currentTime;
|
|
26
|
+
}
|
|
27
|
+
play() {
|
|
28
|
+
this.media.play();
|
|
29
|
+
}
|
|
30
|
+
pause() {
|
|
31
|
+
this.media.pause();
|
|
32
|
+
}
|
|
33
|
+
isPlaying() {
|
|
34
|
+
return this.media.currentTime > 0 && !this.media.paused && !this.media.ended;
|
|
35
|
+
}
|
|
36
|
+
seekTo(time) {
|
|
37
|
+
this.media.currentTime = time;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export default Player;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import BasePlugin from '../base-plugin.js';
|
|
2
|
+
import { WaveSurferPluginParams } from '../index.js';
|
|
3
|
+
type Region = {
|
|
4
|
+
startTime: number;
|
|
5
|
+
endTime: number;
|
|
6
|
+
title: string;
|
|
7
|
+
start: number;
|
|
8
|
+
end: number;
|
|
9
|
+
element: HTMLElement;
|
|
10
|
+
};
|
|
11
|
+
type RegionsPluginEvents = {
|
|
12
|
+
'region-created': {
|
|
13
|
+
region: Region;
|
|
14
|
+
};
|
|
15
|
+
'region-updated': {
|
|
16
|
+
region: Region;
|
|
17
|
+
};
|
|
18
|
+
'region-clicked': {
|
|
19
|
+
region: Region;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
declare class RegionsPlugin extends BasePlugin<RegionsPluginEvents> {
|
|
23
|
+
private dragStart;
|
|
24
|
+
private container;
|
|
25
|
+
private regions;
|
|
26
|
+
private createdRegion;
|
|
27
|
+
private modifiedRegion;
|
|
28
|
+
private isResizingLeft;
|
|
29
|
+
private isMoving;
|
|
30
|
+
/** Create an instance of RegionsPlugin */
|
|
31
|
+
constructor(params: WaveSurferPluginParams);
|
|
32
|
+
/** Unmounts the regions */
|
|
33
|
+
destroy(): void;
|
|
34
|
+
private initContainer;
|
|
35
|
+
private handleMouseDown;
|
|
36
|
+
private handleMouseMove;
|
|
37
|
+
private handleMouseUp;
|
|
38
|
+
private createRegionElement;
|
|
39
|
+
private createRegion;
|
|
40
|
+
private addRegion;
|
|
41
|
+
private updateRegion;
|
|
42
|
+
private moveRegion;
|
|
43
|
+
/** Create a region at a given start and end time, with an optional title */
|
|
44
|
+
addRegionAtTime(startTime: number, endTime: number, title?: string): Region;
|
|
45
|
+
/** Set the background color of a region */
|
|
46
|
+
setRegionColor(region: Region, color: string): void;
|
|
47
|
+
}
|
|
48
|
+
export default RegionsPlugin;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import BasePlugin from '../base-plugin.js';
|
|
2
|
+
const MIN_WIDTH = 10;
|
|
3
|
+
class RegionsPlugin extends BasePlugin {
|
|
4
|
+
/** Create an instance of RegionsPlugin */
|
|
5
|
+
constructor(params) {
|
|
6
|
+
super(params);
|
|
7
|
+
this.dragStart = NaN;
|
|
8
|
+
this.regions = [];
|
|
9
|
+
this.createdRegion = null;
|
|
10
|
+
this.modifiedRegion = null;
|
|
11
|
+
this.isResizingLeft = false;
|
|
12
|
+
this.isMoving = false;
|
|
13
|
+
this.handleMouseDown = (e) => {
|
|
14
|
+
this.dragStart = e.clientX - this.container.getBoundingClientRect().left;
|
|
15
|
+
};
|
|
16
|
+
this.handleMouseMove = (e) => {
|
|
17
|
+
const dragEnd = e.clientX - this.container.getBoundingClientRect().left;
|
|
18
|
+
if (this.modifiedRegion && this.isMoving) {
|
|
19
|
+
this.moveRegion(this.modifiedRegion, dragEnd - this.dragStart);
|
|
20
|
+
this.dragStart = dragEnd;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (this.modifiedRegion) {
|
|
24
|
+
this.updateRegion(this.modifiedRegion, this.isResizingLeft ? dragEnd : undefined, this.isResizingLeft ? undefined : dragEnd);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!isNaN(this.dragStart)) {
|
|
28
|
+
const dragEnd = e.clientX - this.container.getBoundingClientRect().left;
|
|
29
|
+
if (dragEnd - this.dragStart >= MIN_WIDTH) {
|
|
30
|
+
if (!this.createdRegion) {
|
|
31
|
+
this.renderer.getContainer().style.pointerEvents = 'none';
|
|
32
|
+
this.createdRegion = this.createRegion(this.dragStart, dragEnd);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
this.updateRegion(this.createdRegion, this.dragStart, dragEnd);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
this.handleMouseUp = () => {
|
|
41
|
+
if (this.createdRegion) {
|
|
42
|
+
this.addRegion(this.createdRegion);
|
|
43
|
+
this.createdRegion = null;
|
|
44
|
+
}
|
|
45
|
+
this.modifiedRegion = null;
|
|
46
|
+
this.isMoving = false;
|
|
47
|
+
this.dragStart = NaN;
|
|
48
|
+
this.renderer.getContainer().style.pointerEvents = '';
|
|
49
|
+
};
|
|
50
|
+
this.container = this.initContainer();
|
|
51
|
+
const unsubscribeReady = this.wavesurfer.on('ready', () => {
|
|
52
|
+
const wrapper = this.renderer.getContainer();
|
|
53
|
+
wrapper.appendChild(this.container);
|
|
54
|
+
unsubscribeReady();
|
|
55
|
+
});
|
|
56
|
+
this.subscriptions.push(unsubscribeReady);
|
|
57
|
+
const wsContainer = this.renderer.getContainer();
|
|
58
|
+
wsContainer.addEventListener('mousedown', this.handleMouseDown);
|
|
59
|
+
document.addEventListener('mousemove', this.handleMouseMove);
|
|
60
|
+
document.addEventListener('mouseup', this.handleMouseUp);
|
|
61
|
+
}
|
|
62
|
+
/** Unmounts the regions */
|
|
63
|
+
destroy() {
|
|
64
|
+
var _a;
|
|
65
|
+
this.renderer.getContainer().removeEventListener('mousedown', this.handleMouseDown);
|
|
66
|
+
document.removeEventListener('mousemove', this.handleMouseMove);
|
|
67
|
+
document.removeEventListener('mouseup', this.handleMouseUp, true);
|
|
68
|
+
(_a = this.container) === null || _a === void 0 ? void 0 : _a.remove();
|
|
69
|
+
super.destroy();
|
|
70
|
+
}
|
|
71
|
+
initContainer() {
|
|
72
|
+
const div = document.createElement('div');
|
|
73
|
+
div.style.position = 'absolute';
|
|
74
|
+
div.style.top = '0';
|
|
75
|
+
div.style.left = '0';
|
|
76
|
+
div.style.width = '100%';
|
|
77
|
+
div.style.height = '100%';
|
|
78
|
+
div.style.zIndex = '3';
|
|
79
|
+
div.style.pointerEvents = 'none';
|
|
80
|
+
return div;
|
|
81
|
+
}
|
|
82
|
+
createRegionElement(start, end, title = '') {
|
|
83
|
+
const el = document.createElement('div');
|
|
84
|
+
el.style.position = 'absolute';
|
|
85
|
+
el.style.left = `${start}px`;
|
|
86
|
+
el.style.width = `${end - start}px`;
|
|
87
|
+
el.style.height = '100%';
|
|
88
|
+
el.style.backgroundColor = 'rgba(0, 0, 0, 0.1)';
|
|
89
|
+
el.style.transition = 'background-color 0.2s ease';
|
|
90
|
+
el.style.cursor = 'move';
|
|
91
|
+
el.style.pointerEvents = 'all';
|
|
92
|
+
el.title = title;
|
|
93
|
+
const leftHandle = document.createElement('div');
|
|
94
|
+
leftHandle.style.position = 'absolute';
|
|
95
|
+
leftHandle.style.left = '0';
|
|
96
|
+
leftHandle.style.width = '6px';
|
|
97
|
+
leftHandle.style.height = '100%';
|
|
98
|
+
leftHandle.style.borderLeft = '2px solid rgba(0, 0, 0, 0.5)';
|
|
99
|
+
leftHandle.style.cursor = 'ew-resize';
|
|
100
|
+
leftHandle.style.pointerEvents = 'all';
|
|
101
|
+
el.appendChild(leftHandle);
|
|
102
|
+
const rightHandle = document.createElement('div');
|
|
103
|
+
rightHandle.style.position = 'absolute';
|
|
104
|
+
rightHandle.style.right = '0';
|
|
105
|
+
rightHandle.style.width = '6px';
|
|
106
|
+
rightHandle.style.height = '100%';
|
|
107
|
+
rightHandle.style.borderRight = '2px solid rgba(0, 0, 0, 0.5)';
|
|
108
|
+
rightHandle.style.cursor = 'ew-resize';
|
|
109
|
+
rightHandle.style.pointerEvents = 'all';
|
|
110
|
+
el.appendChild(rightHandle);
|
|
111
|
+
leftHandle.addEventListener('mousedown', (e) => {
|
|
112
|
+
e.stopPropagation();
|
|
113
|
+
this.modifiedRegion = this.regions.find((r) => r.element === el) || null;
|
|
114
|
+
this.isResizingLeft = true;
|
|
115
|
+
this.isMoving = false;
|
|
116
|
+
});
|
|
117
|
+
rightHandle.addEventListener('mousedown', (e) => {
|
|
118
|
+
e.stopPropagation();
|
|
119
|
+
this.modifiedRegion = this.regions.find((r) => r.element === el) || null;
|
|
120
|
+
this.isResizingLeft = false;
|
|
121
|
+
this.isMoving = false;
|
|
122
|
+
});
|
|
123
|
+
el.addEventListener('mousedown', () => {
|
|
124
|
+
this.modifiedRegion = this.regions.find((r) => r.element === el) || null;
|
|
125
|
+
this.isMoving = true;
|
|
126
|
+
});
|
|
127
|
+
el.addEventListener('click', () => {
|
|
128
|
+
const region = this.regions.find((r) => r.element === el);
|
|
129
|
+
if (region) {
|
|
130
|
+
this.emit('region-clicked', { region });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
this.container.appendChild(el);
|
|
134
|
+
return el;
|
|
135
|
+
}
|
|
136
|
+
createRegion(start, end, title = '') {
|
|
137
|
+
const duration = this.wavesurfer.getDuration();
|
|
138
|
+
const width = this.container.clientWidth;
|
|
139
|
+
return {
|
|
140
|
+
element: this.createRegionElement(start, end, title),
|
|
141
|
+
start,
|
|
142
|
+
end,
|
|
143
|
+
startTime: (start / width) * duration,
|
|
144
|
+
endTime: (end / width) * duration,
|
|
145
|
+
title,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
addRegion(region) {
|
|
149
|
+
this.regions.push(region);
|
|
150
|
+
this.emit('region-created', { region });
|
|
151
|
+
}
|
|
152
|
+
updateRegion(region, start, end) {
|
|
153
|
+
if (start != null) {
|
|
154
|
+
region.start = start !== null && start !== void 0 ? start : region.start;
|
|
155
|
+
region.element.style.left = `${region.start}px`;
|
|
156
|
+
region.startTime = (start / this.container.clientWidth) * this.wavesurfer.getDuration();
|
|
157
|
+
}
|
|
158
|
+
if (end != null) {
|
|
159
|
+
region.end = end;
|
|
160
|
+
region.element.style.width = `${region.end - region.start}px`;
|
|
161
|
+
region.endTime = (end / this.container.clientWidth) * this.wavesurfer.getDuration();
|
|
162
|
+
}
|
|
163
|
+
this.emit('region-updated', { region });
|
|
164
|
+
}
|
|
165
|
+
moveRegion(region, delta) {
|
|
166
|
+
this.updateRegion(region, region.start + delta, region.end + delta);
|
|
167
|
+
}
|
|
168
|
+
/** Create a region at a given start and end time, with an optional title */
|
|
169
|
+
addRegionAtTime(startTime, endTime, title = '') {
|
|
170
|
+
const duration = this.wavesurfer.getDuration();
|
|
171
|
+
const width = this.container.clientWidth;
|
|
172
|
+
const start = (startTime / duration) * width;
|
|
173
|
+
const end = (endTime / duration) * width;
|
|
174
|
+
const region = this.createRegion(start, end, title);
|
|
175
|
+
this.addRegion(region);
|
|
176
|
+
return region;
|
|
177
|
+
}
|
|
178
|
+
/** Set the background color of a region */
|
|
179
|
+
setRegionColor(region, color) {
|
|
180
|
+
region.element.style.backgroundColor = color;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export default RegionsPlugin;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import EventEmitter from './event-emitter.js';
|
|
2
|
+
type RendererOptions = {
|
|
3
|
+
container: HTMLElement | string | null;
|
|
4
|
+
height: number;
|
|
5
|
+
waveColor: string;
|
|
6
|
+
progressColor: string;
|
|
7
|
+
minPxPerSec: number;
|
|
8
|
+
barWidth?: number;
|
|
9
|
+
barGap?: number;
|
|
10
|
+
barRadius?: number;
|
|
11
|
+
};
|
|
12
|
+
type RendererEvents = {
|
|
13
|
+
click: {
|
|
14
|
+
relativeX: number;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
declare class Renderer extends EventEmitter<RendererEvents> {
|
|
18
|
+
private options;
|
|
19
|
+
private container;
|
|
20
|
+
private shadowRoot;
|
|
21
|
+
private mainCanvas;
|
|
22
|
+
private progressCanvas;
|
|
23
|
+
private cursor;
|
|
24
|
+
private ctx;
|
|
25
|
+
constructor(options: RendererOptions);
|
|
26
|
+
destroy(): void;
|
|
27
|
+
private renderLinePeaks;
|
|
28
|
+
private renderBarPeaks;
|
|
29
|
+
render(channelData: Float32Array[], duration: number, minPxPerSec?: number): void;
|
|
30
|
+
private createProgressMask;
|
|
31
|
+
renderProgress(progress: number, autoCenter?: boolean): void;
|
|
32
|
+
getContainer(): HTMLElement;
|
|
33
|
+
}
|
|
34
|
+
export default Renderer;
|
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import EventEmitter from './event-emitter.js';
|
|
2
|
+
class Renderer extends EventEmitter {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
super();
|
|
5
|
+
this.options = options;
|
|
6
|
+
let container = null;
|
|
7
|
+
if (typeof this.options.container === 'string') {
|
|
8
|
+
container = document.querySelector(this.options.container);
|
|
9
|
+
}
|
|
10
|
+
else if (this.options.container instanceof HTMLElement) {
|
|
11
|
+
container = this.options.container;
|
|
12
|
+
}
|
|
13
|
+
if (!container) {
|
|
14
|
+
throw new Error('Container not found');
|
|
15
|
+
}
|
|
16
|
+
const div = document.createElement('div');
|
|
17
|
+
const shadow = div.attachShadow({ mode: 'open' });
|
|
18
|
+
shadow.innerHTML = `
|
|
19
|
+
<style>
|
|
20
|
+
:host .scroll {
|
|
21
|
+
overflow-x: auto;
|
|
22
|
+
overflow-y: visible;
|
|
23
|
+
user-select: none;
|
|
24
|
+
width: 100%;
|
|
25
|
+
height: ${this.options.height}px;
|
|
26
|
+
position: relative;
|
|
27
|
+
}
|
|
28
|
+
:host .wrapper {
|
|
29
|
+
position: relative;
|
|
30
|
+
width: fit-content;
|
|
31
|
+
min-width: 100%;
|
|
32
|
+
height: 100%;
|
|
33
|
+
z-index: 2;
|
|
34
|
+
}
|
|
35
|
+
:host canvas {
|
|
36
|
+
display: block;
|
|
37
|
+
height: 100%;
|
|
38
|
+
min-width: 100%;
|
|
39
|
+
image-rendering: pixelated;
|
|
40
|
+
}
|
|
41
|
+
:host .progress {
|
|
42
|
+
position: absolute;
|
|
43
|
+
z-index: 2;
|
|
44
|
+
top: 0;
|
|
45
|
+
left: 0;
|
|
46
|
+
height: 100%;
|
|
47
|
+
pointer-events: none;
|
|
48
|
+
clip-path: inset(100%);
|
|
49
|
+
}
|
|
50
|
+
:host .cursor {
|
|
51
|
+
position: absolute;
|
|
52
|
+
z-index: 3;
|
|
53
|
+
top: 0;
|
|
54
|
+
left: 0;
|
|
55
|
+
height: 100%;
|
|
56
|
+
border-right: 1px solid ${this.options.progressColor};
|
|
57
|
+
}
|
|
58
|
+
</style>
|
|
59
|
+
|
|
60
|
+
<div class="scroll">
|
|
61
|
+
<div class="wrapper">
|
|
62
|
+
<canvas></canvas>
|
|
63
|
+
<canvas class="progress"></canvas>
|
|
64
|
+
<div class="cursor"></div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
`;
|
|
68
|
+
this.container = div;
|
|
69
|
+
this.shadowRoot = shadow;
|
|
70
|
+
this.mainCanvas = shadow.querySelector('canvas');
|
|
71
|
+
this.ctx = this.mainCanvas.getContext('2d', { desynchronized: true });
|
|
72
|
+
this.progressCanvas = shadow.querySelector('.progress');
|
|
73
|
+
this.cursor = shadow.querySelector('.cursor');
|
|
74
|
+
container.appendChild(div);
|
|
75
|
+
this.mainCanvas.addEventListener('click', (e) => {
|
|
76
|
+
const rect = this.mainCanvas.getBoundingClientRect();
|
|
77
|
+
const x = e.clientX - rect.left;
|
|
78
|
+
const relativeX = x / rect.width;
|
|
79
|
+
this.emit('click', { relativeX });
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
destroy() {
|
|
83
|
+
this.container.remove();
|
|
84
|
+
}
|
|
85
|
+
renderLinePeaks(channelData, width, height) {
|
|
86
|
+
const { ctx } = this;
|
|
87
|
+
ctx.clearRect(0, 0, width, height);
|
|
88
|
+
ctx.beginPath();
|
|
89
|
+
ctx.moveTo(0, height / 2);
|
|
90
|
+
// Channel 0 is left, 1 is right
|
|
91
|
+
const leftChannel = channelData[0];
|
|
92
|
+
const isMono = channelData.length === 1;
|
|
93
|
+
const rightChannel = isMono ? leftChannel : channelData[1];
|
|
94
|
+
// Draw left channel in the top half of the canvas
|
|
95
|
+
let prevX = -1;
|
|
96
|
+
let prevY = 0;
|
|
97
|
+
for (let i = 0, len = leftChannel.length; i < len; i++) {
|
|
98
|
+
const x = Math.round((i / leftChannel.length) * width);
|
|
99
|
+
const y = Math.round(((1 - leftChannel[i]) * height) / 2);
|
|
100
|
+
if (x !== prevX || y > prevY) {
|
|
101
|
+
ctx.lineTo(x, y);
|
|
102
|
+
prevX = x;
|
|
103
|
+
prevY = y;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Draw right channel in the bottom half of the canvas
|
|
107
|
+
prevX = -1;
|
|
108
|
+
prevY = 0;
|
|
109
|
+
for (let i = rightChannel.length - 1; i >= 0; i--) {
|
|
110
|
+
const x = Math.round((i / rightChannel.length) * width);
|
|
111
|
+
const y = Math.round(((1 + rightChannel[i]) * height) / 2);
|
|
112
|
+
if (x !== prevX || (isMono ? y < -prevY : y > prevY)) {
|
|
113
|
+
ctx.lineTo(x, y);
|
|
114
|
+
prevX = x;
|
|
115
|
+
prevY = y;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
ctx.strokeStyle = ctx.fillStyle = this.options.waveColor;
|
|
119
|
+
ctx.stroke();
|
|
120
|
+
ctx.fill();
|
|
121
|
+
}
|
|
122
|
+
renderBarPeaks(channelData, width, height) {
|
|
123
|
+
var _a, _b;
|
|
124
|
+
const { devicePixelRatio } = window;
|
|
125
|
+
const { ctx } = this;
|
|
126
|
+
ctx.clearRect(0, 0, width, height);
|
|
127
|
+
const barWidthOption = this.options.barWidth || 1;
|
|
128
|
+
const barWidth = barWidthOption * devicePixelRatio;
|
|
129
|
+
const barGap = ((_a = this.options.barGap) !== null && _a !== void 0 ? _a : barWidthOption / 2) * devicePixelRatio;
|
|
130
|
+
const barRadius = (_b = this.options.barRadius) !== null && _b !== void 0 ? _b : 0;
|
|
131
|
+
const leftChannel = channelData[0];
|
|
132
|
+
const isMono = channelData.length === 1;
|
|
133
|
+
const rightChannel = isMono ? leftChannel : channelData[1];
|
|
134
|
+
const barCount = Math.floor(width / (barWidth + barGap));
|
|
135
|
+
const leftChannelBars = new Float32Array(barCount);
|
|
136
|
+
const rightChannelBars = new Float32Array(barCount);
|
|
137
|
+
const barIndexScale = barCount / leftChannel.length;
|
|
138
|
+
const leftChannelLength = leftChannel.length;
|
|
139
|
+
for (let i = 0; i < leftChannelLength; i++) {
|
|
140
|
+
const barIndex = Math.round(i * barIndexScale);
|
|
141
|
+
leftChannelBars[barIndex] = Math.max(leftChannelBars[barIndex], leftChannel[i]);
|
|
142
|
+
rightChannelBars[barIndex] = (isMono ? Math.min : Math.max)(rightChannelBars[barIndex], rightChannel[i]);
|
|
143
|
+
}
|
|
144
|
+
ctx.beginPath();
|
|
145
|
+
for (let i = 0; i < barCount; i++) {
|
|
146
|
+
const leftBarHeight = Math.max(1, Math.round((leftChannelBars[i] * height) / 2));
|
|
147
|
+
const rightBarHeight = Math.max(1, Math.round(Math.abs(rightChannelBars[i] * height) / 2));
|
|
148
|
+
ctx.roundRect(i * (barWidth + barGap), height / 2 - leftBarHeight, barWidth, leftBarHeight + rightBarHeight, barRadius);
|
|
149
|
+
}
|
|
150
|
+
ctx.fillStyle = this.options.waveColor;
|
|
151
|
+
ctx.fill();
|
|
152
|
+
}
|
|
153
|
+
render(channelData, duration, minPxPerSec = this.options.minPxPerSec) {
|
|
154
|
+
const { devicePixelRatio } = window;
|
|
155
|
+
const width = Math.max(this.container.clientWidth * devicePixelRatio, duration * minPxPerSec);
|
|
156
|
+
const { height } = this.options;
|
|
157
|
+
this.mainCanvas.width = width;
|
|
158
|
+
this.mainCanvas.height = height;
|
|
159
|
+
this.mainCanvas.style.width = Math.round(width / devicePixelRatio) + 'px';
|
|
160
|
+
const renderingFn = this.options.barWidth ? this.renderBarPeaks : this.renderLinePeaks;
|
|
161
|
+
renderingFn.call(this, channelData, width, height);
|
|
162
|
+
this.createProgressMask();
|
|
163
|
+
}
|
|
164
|
+
createProgressMask() {
|
|
165
|
+
const progressCtx = this.progressCanvas.getContext('2d', { desynchronized: true });
|
|
166
|
+
if (!progressCtx) {
|
|
167
|
+
throw new Error('Failed to get canvas context');
|
|
168
|
+
}
|
|
169
|
+
this.progressCanvas.width = this.mainCanvas.width;
|
|
170
|
+
this.progressCanvas.height = this.mainCanvas.height;
|
|
171
|
+
this.progressCanvas.style.width = this.mainCanvas.style.width;
|
|
172
|
+
this.progressCanvas.style.height = this.mainCanvas.style.height;
|
|
173
|
+
progressCtx.drawImage(this.mainCanvas, 0, 0);
|
|
174
|
+
progressCtx.globalCompositeOperation = 'source-in';
|
|
175
|
+
progressCtx.fillStyle = this.options.progressColor;
|
|
176
|
+
progressCtx.fillRect(0, 0, this.progressCanvas.width, this.progressCanvas.height);
|
|
177
|
+
}
|
|
178
|
+
renderProgress(progress, autoCenter = false) {
|
|
179
|
+
this.progressCanvas.style.clipPath = `inset(0 ${100 - progress * 100}% 0 0)`;
|
|
180
|
+
this.cursor.style.left = `${progress * 100}%`;
|
|
181
|
+
if (autoCenter) {
|
|
182
|
+
const center = this.container.clientWidth / 2;
|
|
183
|
+
const fullWidth = this.mainCanvas.clientWidth;
|
|
184
|
+
if (fullWidth * progress >= center) {
|
|
185
|
+
this.container.scrollLeft = fullWidth * progress - center;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
getContainer() {
|
|
190
|
+
return this.shadowRoot.querySelector('.scroll');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
export default Renderer;
|
package/dist/timer.d.ts
ADDED
package/dist/timer.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import EventEmitter from './event-emitter.js';
|
|
2
|
+
class Timer extends EventEmitter {
|
|
3
|
+
constructor() {
|
|
4
|
+
super();
|
|
5
|
+
this.unsubscribe = () => undefined;
|
|
6
|
+
this.unsubscribe = this.on('tick', () => {
|
|
7
|
+
requestAnimationFrame(() => {
|
|
8
|
+
this.emit('tick');
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
this.emit('tick');
|
|
12
|
+
}
|
|
13
|
+
destroy() {
|
|
14
|
+
this.unsubscribe();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export default Timer;
|
|
Binary file
|
package/examples/bars.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import WaveSurfer from '/dist/index.js'
|
|
2
|
+
|
|
3
|
+
const wavesurfer = WaveSurfer.create({
|
|
4
|
+
container: document.body,
|
|
5
|
+
waveColor: 'rgb(200, 0, 200)',
|
|
6
|
+
progressColor: 'rgb(100, 0, 100)',
|
|
7
|
+
url: 'https://wavesurfer-js.org/example/media/demo.wav',
|
|
8
|
+
|
|
9
|
+
// Set a bar width
|
|
10
|
+
barWidth: 2,
|
|
11
|
+
// Optionally, specify the spacing between bars
|
|
12
|
+
barGap: 1,
|
|
13
|
+
// And the bar radius
|
|
14
|
+
barRadius: 2,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
wavesurfer.on('seek', function () {
|
|
18
|
+
wavesurfer.play()
|
|
19
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import WaveSurfer from '/dist/index.js'
|
|
2
|
+
|
|
3
|
+
// Create a canvas gradient
|
|
4
|
+
const ctx = document.createElement('canvas').getContext('2d')
|
|
5
|
+
const gradient = ctx.createLinearGradient(0, 0, 0, 150)
|
|
6
|
+
gradient.addColorStop(0, 'rgb(200, 0, 200)')
|
|
7
|
+
gradient.addColorStop(0.7, 'rgb(100, 0, 100)')
|
|
8
|
+
gradient.addColorStop(1, 'rgb(0, 0, 0)')
|
|
9
|
+
|
|
10
|
+
// Default style with a gradient
|
|
11
|
+
WaveSurfer.create({
|
|
12
|
+
container: document.body,
|
|
13
|
+
waveColor: gradient,
|
|
14
|
+
height: 200,
|
|
15
|
+
progressColor: 'rgba(0, 0, 100, 0.5)',
|
|
16
|
+
url: 'https://wavesurfer-js.org/example/media/demo.wav'
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
// SoundCloud-style bars
|
|
21
|
+
WaveSurfer.create({
|
|
22
|
+
container: document.body,
|
|
23
|
+
waveColor: gradient,
|
|
24
|
+
height: 200,
|
|
25
|
+
barWidth: 2,
|
|
26
|
+
progressColor: 'rgba(0, 0, 100, 0.5)',
|
|
27
|
+
url: 'https://wavesurfer-js.org/example/media/demo.wav'
|
|
28
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import WaveSurfer from '/dist/index.js'
|
|
2
|
+
import RegionsPlugin from '/dist/plugins/regions.js'
|
|
3
|
+
|
|
4
|
+
// Create an instance of WaveSurfer
|
|
5
|
+
const ws = WaveSurfer.create({
|
|
6
|
+
container: document.body,
|
|
7
|
+
waveColor: 'rgb(200, 0, 200)',
|
|
8
|
+
progressColor: 'rgb(100, 0, 100)',
|
|
9
|
+
url: 'https://wavesurfer-js.org/example/media/demo.wav'
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
// Initialize the Regions plugin
|
|
13
|
+
const wsRegions = ws.registerPlugin(RegionsPlugin)
|
|
14
|
+
|
|
15
|
+
// Give regions a random color when they are created
|
|
16
|
+
const random = (min, max) => Math.random() * (max - min) + min
|
|
17
|
+
wsRegions.on('region-created', ({ region }) => {
|
|
18
|
+
wsRegions.setRegionColor(region, `rgba(${random(0, 255)}, ${random(0, 255)}, ${random(0, 255)}, 0.5)`)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// Create some regions at specific time ranges
|
|
22
|
+
ws.on('ready', () => {
|
|
23
|
+
wsRegions.addRegionAtTime(4, 7, 'First region')
|
|
24
|
+
wsRegions.addRegionAtTime(9, 10, 'Middle region')
|
|
25
|
+
wsRegions.addRegionAtTime(12, 17, 'Last region')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// Loop a region on click
|
|
29
|
+
let loop = true
|
|
30
|
+
let activeRegion = null
|
|
31
|
+
|
|
32
|
+
wsRegions.on('region-clicked', ({ region }) => {
|
|
33
|
+
ws.seekTo(region.startTime)
|
|
34
|
+
ws.play()
|
|
35
|
+
activeRegion = region
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// Track the time
|
|
39
|
+
ws.on('audioprocess', ({ currentTime }) => {
|
|
40
|
+
// When the end of the region is reached
|
|
41
|
+
if (activeRegion && ws.isPlaying() && (currentTime >= activeRegion.endTime)) {
|
|
42
|
+
if (loop) {
|
|
43
|
+
// If looping, jump to the start of the region
|
|
44
|
+
ws.seekTo(activeRegion.startTime)
|
|
45
|
+
} else {
|
|
46
|
+
// Otherwise, stop playing
|
|
47
|
+
ws.pause()
|
|
48
|
+
ws.seekTo(activeRegion.endTime)
|
|
49
|
+
activeRegion = null
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// Toggle looping with a checkbox
|
|
55
|
+
const p = document.createElement('p')
|
|
56
|
+
p.innerHTML = `
|
|
57
|
+
<label>
|
|
58
|
+
<input type="checkbox" checked="${loop}" style="vertical-align: text-top" />
|
|
59
|
+
Loop regions on click
|
|
60
|
+
</label>
|
|
61
|
+
`
|
|
62
|
+
document.body.appendChild(p)
|
|
63
|
+
document.querySelector('input').onclick = (e) => { loop = e.target.checked }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import WaveSurfer from '/dist/index.js'
|
|
2
|
+
|
|
3
|
+
// Create a video element and add it to the DOM.
|
|
4
|
+
// The video can also be defined in HTML, in which case we just get it by a selector.
|
|
5
|
+
const video = document.createElement('video')
|
|
6
|
+
video.crossOrigin = 'anonymous'
|
|
7
|
+
video.controls = true
|
|
8
|
+
video.style.width = '100%'
|
|
9
|
+
video.src = 'https://wavesurfer-js.org/example/media/nasa.mp4'
|
|
10
|
+
document.body.appendChild(video)
|
|
11
|
+
|
|
12
|
+
// Initialize wavesurfer.js
|
|
13
|
+
const ws = WaveSurfer.create({
|
|
14
|
+
container: document.body,
|
|
15
|
+
waveColor: 'rgb(200, 0, 200)',
|
|
16
|
+
progressColor: 'rgb(100, 0, 100)',
|
|
17
|
+
// Pass the video element in the `media` param
|
|
18
|
+
media: document.querySelector('video'),
|
|
19
|
+
})
|