wavesurfer.js 7.0.0-alpha.9 → 7.0.0-beta.2
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 +90 -18
- package/dist/base-plugin.d.ts +6 -4
- package/dist/base-plugin.js +9 -3
- package/dist/decoder.d.ts +8 -7
- package/dist/decoder.js +43 -38
- package/dist/event-emitter.d.ts +16 -10
- package/dist/event-emitter.js +38 -16
- package/dist/fetcher.d.ts +4 -3
- package/dist/fetcher.js +5 -15
- package/dist/player.d.ts +31 -11
- package/dist/player.js +58 -31
- package/dist/plugins/envelope.d.ts +71 -0
- package/dist/plugins/envelope.js +347 -0
- package/dist/plugins/envelope.min.cjs +1 -0
- package/dist/plugins/envelope.min.js +1 -0
- package/dist/plugins/minimap.d.ts +39 -0
- package/dist/plugins/minimap.js +114 -0
- package/dist/plugins/minimap.min.cjs +1 -0
- package/dist/plugins/minimap.min.js +1 -0
- package/dist/plugins/multitrack.d.ts +85 -12
- package/dist/plugins/multitrack.js +331 -84
- package/dist/plugins/multitrack.min.cjs +1 -0
- package/dist/plugins/multitrack.min.js +1 -0
- package/dist/plugins/record.d.ts +26 -0
- package/dist/plugins/record.js +126 -0
- package/dist/plugins/record.min.cjs +1 -0
- package/dist/plugins/record.min.js +1 -0
- package/dist/plugins/regions.d.ts +83 -43
- package/dist/plugins/regions.js +362 -192
- package/dist/plugins/regions.min.cjs +1 -0
- package/dist/plugins/regions.min.js +1 -0
- package/dist/plugins/spectrogram.d.ts +69 -0
- package/dist/plugins/spectrogram.js +340 -0
- package/dist/plugins/spectrogram.min.cjs +1 -0
- package/dist/plugins/spectrogram.min.js +1 -0
- package/dist/plugins/timeline.d.ts +25 -9
- package/dist/plugins/timeline.js +65 -24
- package/dist/plugins/timeline.min.cjs +1 -0
- package/dist/plugins/timeline.min.js +1 -0
- package/dist/renderer.d.ts +29 -13
- package/dist/renderer.js +280 -161
- package/dist/timer.d.ts +1 -1
- package/dist/wavesurfer.d.ts +151 -0
- package/dist/wavesurfer.js +241 -0
- package/dist/wavesurfer.min.cjs +1 -0
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +55 -28
- package/dist/index.d.ts +0 -117
- package/dist/index.js +0 -227
- 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/wavesurfer.Multitrack.min.js +0 -1
- package/dist/wavesurfer.Regions.min.js +0 -1
- package/dist/wavesurfer.Timeline.min.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Timeline=e():t.Timeline=e()}(WaveSurfer,(()=>(()=>{"use strict";var t={284:(t,e,i)=>{i.d(e,{Z:()=>r});var n=i(139);class s extends n.Z{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.subscriptions.forEach((t=>t()))}}const r=s},139:(t,e,i)=>{i.d(e,{Z:()=>n});const n=class{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const i=this.on(t,e),n=this.on(t,(()=>{i(),n()}));return i}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}}}},e={};function i(n){var s=e[n];if(void 0!==s)return s.exports;var r=e[n]={exports:{}};return t[n](r,r.exports,i),r.exports}i.d=(t,e)=>{for(var n in e)i.o(e,n)&&!i.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var n={};return(()=>{i.d(n,{default:()=>r});var t=i(284);const e={height:20};class s extends t.Z{constructor(t){super(t||{}),this.options=Object.assign({},e,t),this.timelineWrapper=this.initTimelineWrapper()}static create(t){return new s(t)}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");const t=this.options.container??this.wavesurfer.getWrapper();this.options.insertPosition?(t.firstElementChild||t).insertAdjacentElement(this.options.insertPosition,this.timelineWrapper):t.appendChild(this.timelineWrapper),this.options.duration?this.initTimeline(this.options.duration):this.subscriptions.push(this.wavesurfer.on("redraw",(()=>{this.initTimeline(this.wavesurfer?.getDuration()||0)})))}destroy(){this.timelineWrapper.remove(),super.destroy()}initTimelineWrapper(){const t=document.createElement("div");return t.setAttribute("part","timeline"),t}formatTime(t){return t/60>1?`${Math.floor(t/60)}:${(t=Math.round(t%60))<10?"0":""}${t}`:""+Math.round(1e3*t)/1e3}defaultTimeInterval(t){return t>=25?1:5*t>=25?5:15*t>=25?15:60*Math.ceil(.5/t)}defaultPrimaryLabelInterval(t){return t>=25?10:5*t>=25?6:4}defaultSecondaryLabelInterval(t){return t>=25?5:2}initTimeline(t){const e=this.timelineWrapper.scrollWidth/t,i=this.options.timeInterval??this.defaultTimeInterval(e),n=this.options.primaryLabelInterval??this.defaultPrimaryLabelInterval(e),s=this.options.secondaryLabelInterval??this.defaultSecondaryLabelInterval(e),r="beforebegin"===this.options.insertPosition,o=document.createElement("div");if(o.setAttribute("style",`\n height: ${this.options.height}px;\n overflow: hidden;\n display: flex;\n justify-content: space-between;\n align-items: ${r?"flex-start":"flex-end"};\n font-size: ${this.options.height/2}px;\n white-space: nowrap;\n `),r){const t="\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n z-index: 2;\n ";o.setAttribute("style",o.getAttribute("style")+t)}"string"==typeof this.options.style?o.setAttribute("style",o.getAttribute("style")+this.options.style):"object"==typeof this.options.style&&Object.assign(o.style,this.options.style);const l=document.createElement("div");l.setAttribute("style",`\n width: 1px;\n height: 50%;\n display: flex;\n flex-direction: column;\n justify-content: ${r?"flex-start":"flex-end"};\n overflow: visible;\n border-left: 1px solid currentColor;\n opacity: 0.25;\n `);for(let e=0;e<t;e+=i){const t=l.cloneNode(),i=Math.round(100*e)/100%n==0,r=Math.round(100*e)/100%s==0;(i||r)&&(t.style.height="100%",t.style.textIndent="3px",t.textContent=this.formatTime(e),i&&(t.style.opacity="1")),o.appendChild(t)}this.timelineWrapper.innerHTML="",this.timelineWrapper.appendChild(o),this.emit("ready")}}const r=s})(),n.default})()));
|
package/dist/renderer.d.ts
CHANGED
|
@@ -1,38 +1,54 @@
|
|
|
1
1
|
import EventEmitter from './event-emitter.js';
|
|
2
|
-
type
|
|
3
|
-
container: HTMLElement | string | null;
|
|
2
|
+
export type RendererStyleOptions = {
|
|
4
3
|
height: number;
|
|
5
4
|
waveColor: string;
|
|
6
5
|
progressColor: string;
|
|
7
6
|
cursorColor?: string;
|
|
7
|
+
cursorWidth: number;
|
|
8
8
|
minPxPerSec: number;
|
|
9
9
|
fillParent: boolean;
|
|
10
10
|
barWidth?: number;
|
|
11
11
|
barGap?: number;
|
|
12
12
|
barRadius?: number;
|
|
13
|
+
barHeight?: number;
|
|
14
|
+
hideScrollbar?: boolean;
|
|
15
|
+
autoCenter?: boolean;
|
|
16
|
+
autoScroll?: boolean;
|
|
13
17
|
};
|
|
14
18
|
type RendererEvents = {
|
|
15
|
-
click:
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
click: [relativeX: number];
|
|
20
|
+
drag: [relativeX: number];
|
|
21
|
+
scroll: [relativeStart: number, relativeEnd: number];
|
|
22
|
+
render: [];
|
|
18
23
|
};
|
|
19
24
|
declare class Renderer extends EventEmitter<RendererEvents> {
|
|
25
|
+
private static MAX_CANVAS_WIDTH;
|
|
20
26
|
private options;
|
|
21
27
|
private container;
|
|
22
28
|
private scrollContainer;
|
|
23
|
-
private
|
|
24
|
-
private
|
|
29
|
+
private wrapper;
|
|
30
|
+
private canvasWrapper;
|
|
31
|
+
private progressWrapper;
|
|
25
32
|
private cursor;
|
|
26
|
-
private ctx;
|
|
27
33
|
private timeout;
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
private isScrolling;
|
|
35
|
+
private audioData;
|
|
36
|
+
private resizeObserver;
|
|
37
|
+
private isDragging;
|
|
38
|
+
constructor(container: HTMLElement | string | null, options: RendererStyleOptions);
|
|
39
|
+
private initEvents;
|
|
40
|
+
private initDrag;
|
|
41
|
+
private initHtml;
|
|
42
|
+
setOptions(options: RendererStyleOptions): void;
|
|
43
|
+
getWrapper(): HTMLElement;
|
|
44
|
+
getScroll(): number;
|
|
30
45
|
destroy(): void;
|
|
31
46
|
private delay;
|
|
32
47
|
private renderPeaks;
|
|
33
|
-
private createProgressMask;
|
|
34
48
|
render(audioData: AudioBuffer): void;
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
reRender(): void;
|
|
50
|
+
zoom(minPxPerSec: number): void;
|
|
51
|
+
private scrollIntoView;
|
|
52
|
+
renderProgress(progress: number, isPlaying?: boolean): void;
|
|
37
53
|
}
|
|
38
54
|
export default Renderer;
|
package/dist/renderer.js
CHANGED
|
@@ -1,28 +1,77 @@
|
|
|
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 {
|
|
12
|
-
constructor(options) {
|
|
3
|
+
constructor(container, options) {
|
|
13
4
|
super();
|
|
5
|
+
this.options = {
|
|
6
|
+
height: 0,
|
|
7
|
+
};
|
|
14
8
|
this.timeout = null;
|
|
15
|
-
this.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
container =
|
|
9
|
+
this.isScrolling = false;
|
|
10
|
+
this.audioData = null;
|
|
11
|
+
this.resizeObserver = null;
|
|
12
|
+
this.isDragging = false;
|
|
13
|
+
this.options = { ...options };
|
|
14
|
+
if (typeof container === 'string') {
|
|
15
|
+
container = document.querySelector(container);
|
|
22
16
|
}
|
|
23
17
|
if (!container) {
|
|
24
18
|
throw new Error('Container not found');
|
|
25
19
|
}
|
|
20
|
+
const [div, shadow] = this.initHtml();
|
|
21
|
+
container.appendChild(div);
|
|
22
|
+
this.container = div;
|
|
23
|
+
this.scrollContainer = shadow.querySelector('.scroll');
|
|
24
|
+
this.wrapper = shadow.querySelector('.wrapper');
|
|
25
|
+
this.canvasWrapper = shadow.querySelector('.canvases');
|
|
26
|
+
this.progressWrapper = shadow.querySelector('.progress');
|
|
27
|
+
this.cursor = shadow.querySelector('.cursor');
|
|
28
|
+
this.initEvents();
|
|
29
|
+
}
|
|
30
|
+
initEvents() {
|
|
31
|
+
// Add a click listener
|
|
32
|
+
this.wrapper.addEventListener('click', (e) => {
|
|
33
|
+
const rect = this.wrapper.getBoundingClientRect();
|
|
34
|
+
const x = e.clientX - rect.left;
|
|
35
|
+
const relativeX = x / rect.width;
|
|
36
|
+
this.emit('click', relativeX);
|
|
37
|
+
});
|
|
38
|
+
// Drag
|
|
39
|
+
this.initDrag();
|
|
40
|
+
// Add a scroll listener
|
|
41
|
+
this.scrollContainer.addEventListener('scroll', () => {
|
|
42
|
+
const { scrollLeft, scrollWidth, clientWidth } = this.scrollContainer;
|
|
43
|
+
const startX = scrollLeft / scrollWidth;
|
|
44
|
+
const endX = (scrollLeft + clientWidth) / scrollWidth;
|
|
45
|
+
this.emit('scroll', startX, endX);
|
|
46
|
+
});
|
|
47
|
+
// Re-render the waveform on container resize
|
|
48
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
49
|
+
this.delay(() => this.reRender(), 100);
|
|
50
|
+
});
|
|
51
|
+
this.resizeObserver.observe(this.scrollContainer);
|
|
52
|
+
}
|
|
53
|
+
initDrag() {
|
|
54
|
+
this.wrapper.addEventListener('mousedown', (e) => {
|
|
55
|
+
const minDx = 5;
|
|
56
|
+
const x = e.clientX;
|
|
57
|
+
const move = (e) => {
|
|
58
|
+
const diff = Math.abs(e.clientX - x);
|
|
59
|
+
if (diff >= minDx) {
|
|
60
|
+
this.isDragging = true;
|
|
61
|
+
const rect = this.wrapper.getBoundingClientRect();
|
|
62
|
+
this.emit('drag', Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)));
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const up = () => {
|
|
66
|
+
document.removeEventListener('mousemove', move);
|
|
67
|
+
document.removeEventListener('mouseup', up);
|
|
68
|
+
this.isDragging = false;
|
|
69
|
+
};
|
|
70
|
+
document.addEventListener('mousemove', move);
|
|
71
|
+
document.addEventListener('mouseup', up);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
initHtml() {
|
|
26
75
|
const div = document.createElement('div');
|
|
27
76
|
const shadow = div.attachShadow({ mode: 'open' });
|
|
28
77
|
shadow.innerHTML = `
|
|
@@ -36,66 +85,79 @@ class Renderer extends EventEmitter {
|
|
|
36
85
|
width: 100%;
|
|
37
86
|
position: relative;
|
|
38
87
|
}
|
|
88
|
+
:host .noScrollbar {
|
|
89
|
+
scrollbar-color: transparent;
|
|
90
|
+
scrollbar-width: none;
|
|
91
|
+
}
|
|
92
|
+
:host .noScrollbar::-webkit-scrollbar {
|
|
93
|
+
display: none;
|
|
94
|
+
-webkit-appearance: none;
|
|
95
|
+
}
|
|
39
96
|
:host .wrapper {
|
|
40
97
|
position: relative;
|
|
41
|
-
|
|
42
|
-
min-width: 100%;
|
|
98
|
+
overflow: visible;
|
|
43
99
|
z-index: 2;
|
|
44
100
|
}
|
|
101
|
+
:host .canvases {
|
|
102
|
+
position: relative;
|
|
103
|
+
height: ${this.options.height}px;
|
|
104
|
+
}
|
|
45
105
|
:host canvas {
|
|
46
106
|
display: block;
|
|
47
|
-
|
|
48
|
-
|
|
107
|
+
position: absolute;
|
|
108
|
+
top: 0;
|
|
49
109
|
image-rendering: pixelated;
|
|
110
|
+
height: ${this.options.height}px;
|
|
50
111
|
}
|
|
51
112
|
:host .progress {
|
|
113
|
+
pointer-events: none;
|
|
52
114
|
position: absolute;
|
|
53
115
|
z-index: 2;
|
|
54
116
|
top: 0;
|
|
55
117
|
left: 0;
|
|
56
|
-
|
|
57
|
-
|
|
118
|
+
width: 0;
|
|
119
|
+
height: 100%;
|
|
120
|
+
overflow: hidden;
|
|
58
121
|
}
|
|
59
122
|
:host .cursor {
|
|
123
|
+
pointer-events: none;
|
|
60
124
|
position: absolute;
|
|
61
|
-
z-index:
|
|
125
|
+
z-index: 2;
|
|
62
126
|
top: 0;
|
|
63
127
|
left: 0;
|
|
64
128
|
height: 100%;
|
|
65
|
-
border-
|
|
66
|
-
|
|
129
|
+
border-radius: 2px;
|
|
130
|
+
width: ${this.options.cursorWidth}px;
|
|
131
|
+
background-color: ${this.options.cursorColor || this.options.progressColor};
|
|
67
132
|
}
|
|
68
133
|
</style>
|
|
69
134
|
|
|
70
|
-
<div class="scroll">
|
|
135
|
+
<div class="scroll" part="scroll">
|
|
71
136
|
<div class="wrapper">
|
|
72
|
-
<
|
|
73
|
-
<
|
|
74
|
-
<div class="cursor"></div>
|
|
137
|
+
<div class="canvases"></div>
|
|
138
|
+
<div class="progress"></div>
|
|
139
|
+
<div class="cursor" part="cursor"></div>
|
|
75
140
|
</div>
|
|
76
141
|
</div>
|
|
77
142
|
`;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.
|
|
82
|
-
|
|
83
|
-
this.
|
|
84
|
-
container.appendChild(div);
|
|
85
|
-
this.mainCanvas.addEventListener('click', (e) => {
|
|
86
|
-
const rect = this.mainCanvas.getBoundingClientRect();
|
|
87
|
-
const x = e.clientX - rect.left;
|
|
88
|
-
const relativeX = x / rect.width;
|
|
89
|
-
this.emit('click', { relativeX });
|
|
90
|
-
});
|
|
143
|
+
return [div, shadow];
|
|
144
|
+
}
|
|
145
|
+
setOptions(options) {
|
|
146
|
+
this.options = options;
|
|
147
|
+
// Re-render the waveform
|
|
148
|
+
this.reRender();
|
|
91
149
|
}
|
|
92
|
-
|
|
93
|
-
return this.
|
|
150
|
+
getWrapper() {
|
|
151
|
+
return this.wrapper;
|
|
152
|
+
}
|
|
153
|
+
getScroll() {
|
|
154
|
+
return this.scrollContainer.scrollLeft;
|
|
94
155
|
}
|
|
95
156
|
destroy() {
|
|
96
157
|
this.container.remove();
|
|
158
|
+
this.resizeObserver?.disconnect();
|
|
97
159
|
}
|
|
98
|
-
delay(fn, delayMs =
|
|
160
|
+
delay(fn, delayMs = 10) {
|
|
99
161
|
if (this.timeout) {
|
|
100
162
|
clearTimeout(this.timeout);
|
|
101
163
|
}
|
|
@@ -105,135 +167,192 @@ class Renderer extends EventEmitter {
|
|
|
105
167
|
}, delayMs);
|
|
106
168
|
});
|
|
107
169
|
}
|
|
108
|
-
renderPeaks(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
170
|
+
async renderPeaks(audioData, width, height, pixelRatio) {
|
|
171
|
+
const barWidth = this.options.barWidth != null && !isNaN(this.options.barWidth) ? this.options.barWidth * pixelRatio : 1;
|
|
172
|
+
const barGap = this.options.barGap != null && !isNaN(this.options.barGap)
|
|
173
|
+
? this.options.barGap * pixelRatio
|
|
174
|
+
: this.options.barWidth
|
|
175
|
+
? barWidth / 2
|
|
176
|
+
: 0;
|
|
177
|
+
const barRadius = this.options.barRadius || 0;
|
|
178
|
+
const scaleY = this.options.barHeight || 1;
|
|
179
|
+
const leftChannel = audioData.getChannelData(0);
|
|
180
|
+
const len = leftChannel.length;
|
|
181
|
+
const barCount = Math.floor(width / (barWidth + barGap));
|
|
182
|
+
const barIndexScale = barCount / len;
|
|
183
|
+
const halfHeight = height / 2;
|
|
184
|
+
const isMono = audioData.numberOfChannels === 1;
|
|
185
|
+
const rightChannel = isMono ? leftChannel : audioData.getChannelData(1);
|
|
186
|
+
const useNegative = isMono && rightChannel.some((v) => v < 0);
|
|
187
|
+
const draw = (start, end) => {
|
|
188
|
+
let prevX = 0;
|
|
189
|
+
let prevLeft = 0;
|
|
190
|
+
let prevRight = 0;
|
|
191
|
+
const canvas = document.createElement('canvas');
|
|
192
|
+
canvas.width = Math.round((width * (end - start)) / len);
|
|
193
|
+
canvas.height = this.options.height;
|
|
194
|
+
canvas.style.width = `${Math.floor(canvas.width / pixelRatio)}px`;
|
|
195
|
+
canvas.style.height = `${this.options.height}px`;
|
|
196
|
+
canvas.style.left = `${Math.floor((start * width) / pixelRatio / len)}px`;
|
|
197
|
+
this.canvasWrapper.appendChild(canvas);
|
|
198
|
+
const ctx = canvas.getContext('2d', {
|
|
199
|
+
desynchronized: true,
|
|
200
|
+
});
|
|
201
|
+
ctx.beginPath();
|
|
202
|
+
ctx.fillStyle = this.options.waveColor ?? '';
|
|
203
|
+
// Firefox shim until 2023.04.11
|
|
204
|
+
if (!ctx.roundRect)
|
|
205
|
+
ctx.roundRect = ctx.fillRect;
|
|
206
|
+
for (let i = start; i < end; i++) {
|
|
207
|
+
const barIndex = Math.round((i - start) * barIndexScale);
|
|
208
|
+
if (barIndex > prevX) {
|
|
209
|
+
const leftBarHeight = Math.round(prevLeft * halfHeight * scaleY);
|
|
210
|
+
const rightBarHeight = Math.round(prevRight * halfHeight * scaleY);
|
|
211
|
+
ctx.roundRect(prevX * (barWidth + barGap), halfHeight - leftBarHeight, barWidth, leftBarHeight + (rightBarHeight || 1), barRadius);
|
|
212
|
+
prevX = barIndex;
|
|
213
|
+
prevLeft = 0;
|
|
214
|
+
prevRight = 0;
|
|
215
|
+
}
|
|
216
|
+
const leftValue = useNegative ? leftChannel[i] : Math.abs(leftChannel[i]);
|
|
217
|
+
const rightValue = useNegative ? rightChannel[i] : Math.abs(rightChannel[i]);
|
|
218
|
+
if (leftValue > prevLeft) {
|
|
219
|
+
prevLeft = leftValue;
|
|
220
|
+
}
|
|
221
|
+
// If stereo, both channels are drawn as max values
|
|
222
|
+
// If mono with negative values, the bottom channel will be the min negative values
|
|
223
|
+
if (useNegative ? rightValue < -prevRight : rightValue > prevRight) {
|
|
224
|
+
prevRight = rightValue < 0 ? -rightValue : rightValue;
|
|
153
225
|
}
|
|
154
|
-
ctx.fill();
|
|
155
|
-
ctx.closePath();
|
|
156
|
-
};
|
|
157
|
-
// Clear the canvas
|
|
158
|
-
ctx.clearRect(0, 0, width, height);
|
|
159
|
-
// Draw the currently visible part of the waveform
|
|
160
|
-
const { scrollLeft, scrollWidth, clientWidth } = this.scrollContainer;
|
|
161
|
-
const scale = len / scrollWidth;
|
|
162
|
-
const start = Math.floor(scrollLeft * scale);
|
|
163
|
-
const end = Math.ceil((scrollLeft + clientWidth) * scale);
|
|
164
|
-
draw(start, end);
|
|
165
|
-
// Draw the progress mask
|
|
166
|
-
this.createProgressMask();
|
|
167
|
-
// Draw the rest of the waveform with a timeout for better performance
|
|
168
|
-
if (start > 0) {
|
|
169
|
-
yield this.delay(() => {
|
|
170
|
-
draw(0, start);
|
|
171
|
-
});
|
|
172
226
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
227
|
+
ctx.fill();
|
|
228
|
+
ctx.closePath();
|
|
229
|
+
// Draw a progress canvas
|
|
230
|
+
const progressCanvas = canvas.cloneNode();
|
|
231
|
+
this.progressWrapper.appendChild(progressCanvas);
|
|
232
|
+
const progressCtx = progressCanvas.getContext('2d', {
|
|
233
|
+
desynchronized: true,
|
|
234
|
+
});
|
|
235
|
+
if (canvas.width > 0 && canvas.height > 0) {
|
|
236
|
+
progressCtx.drawImage(canvas, 0, 0);
|
|
177
237
|
}
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
238
|
+
// Set the composition method to draw only where the waveform is drawn
|
|
239
|
+
progressCtx.globalCompositeOperation = 'source-in';
|
|
240
|
+
progressCtx.fillStyle = this.options.progressColor ?? '';
|
|
241
|
+
// This rectangle acts as a mask thanks to the composition method
|
|
242
|
+
progressCtx.fillRect(0, 0, canvas.width, canvas.height);
|
|
243
|
+
};
|
|
244
|
+
// Clear the canvas
|
|
245
|
+
this.canvasWrapper.innerHTML = '';
|
|
246
|
+
this.progressWrapper.innerHTML = '';
|
|
247
|
+
// Determine the currently visible part of the waveform
|
|
248
|
+
const { scrollLeft, scrollWidth, clientWidth } = this.scrollContainer;
|
|
249
|
+
const scale = len / scrollWidth;
|
|
250
|
+
let viewportWidth = Math.min(Renderer.MAX_CANVAS_WIDTH, clientWidth);
|
|
251
|
+
viewportWidth -= viewportWidth % ((barWidth + barGap) / pixelRatio);
|
|
252
|
+
const start = Math.floor(Math.abs(scrollLeft) * scale);
|
|
253
|
+
const end = Math.ceil(start + viewportWidth * scale);
|
|
254
|
+
// Draw the visible portion of the waveform
|
|
255
|
+
draw(start, end);
|
|
256
|
+
// Draw the rest of the waveform with a timeout for better performance
|
|
257
|
+
const step = end - start;
|
|
258
|
+
for (let i = end; i < len; i += step) {
|
|
259
|
+
await this.delay(() => {
|
|
260
|
+
draw(i, Math.min(len, i + step));
|
|
181
261
|
});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
this.progressCanvas.height = this.mainCanvas.height;
|
|
189
|
-
this.progressCanvas.style.width = this.mainCanvas.style.width;
|
|
190
|
-
this.progressCanvas.style.height = this.mainCanvas.style.height;
|
|
191
|
-
// Copy the waveform image to the progress canvas
|
|
192
|
-
// The main canvas itself is used as the source image
|
|
193
|
-
progressCtx.drawImage(this.mainCanvas, 0, 0);
|
|
194
|
-
// Set the composition method to draw only where the waveform is drawn
|
|
195
|
-
progressCtx.globalCompositeOperation = 'source-in';
|
|
196
|
-
progressCtx.fillStyle = this.options.progressColor;
|
|
197
|
-
// This rectangle acts as a mask thanks to the composition method
|
|
198
|
-
progressCtx.fillRect(0, 0, this.progressCanvas.width, this.progressCanvas.height);
|
|
262
|
+
}
|
|
263
|
+
for (let i = start - 1; i >= 0; i -= step) {
|
|
264
|
+
await this.delay(() => {
|
|
265
|
+
draw(Math.max(0, i - step), i);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
199
268
|
}
|
|
200
269
|
render(audioData) {
|
|
201
|
-
// Determine the width of the
|
|
202
|
-
const
|
|
203
|
-
const parentWidth = this.
|
|
204
|
-
const scrollWidth = audioData.duration * this.options.minPxPerSec;
|
|
205
|
-
|
|
206
|
-
|
|
270
|
+
// Determine the width of the waveform
|
|
271
|
+
const pixelRatio = window.devicePixelRatio || 1;
|
|
272
|
+
const parentWidth = this.scrollContainer.clientWidth;
|
|
273
|
+
const scrollWidth = Math.ceil(audioData.duration * (this.options.minPxPerSec || 0));
|
|
274
|
+
// Whether the container should scroll
|
|
275
|
+
this.isScrolling = scrollWidth > parentWidth;
|
|
276
|
+
const useParentWidth = this.options.fillParent && !this.isScrolling;
|
|
277
|
+
// Width and height of the waveform in pixels
|
|
278
|
+
const width = (useParentWidth ? parentWidth : scrollWidth) * pixelRatio;
|
|
207
279
|
const { height } = this.options;
|
|
208
|
-
|
|
209
|
-
this.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
280
|
+
// Set the width of the wrapper
|
|
281
|
+
this.wrapper.style.width = useParentWidth ? '100%' : `${scrollWidth}px`;
|
|
282
|
+
// Set additional styles
|
|
283
|
+
this.scrollContainer.style.overflowX = this.isScrolling ? 'auto' : 'hidden';
|
|
284
|
+
this.scrollContainer.classList.toggle('noScrollbar', !!this.options.hideScrollbar);
|
|
285
|
+
this.cursor.style.backgroundColor = `${this.options.cursorColor || this.options.progressColor}`;
|
|
286
|
+
this.cursor.style.width = `${this.options.cursorWidth}px`;
|
|
287
|
+
this.canvasWrapper.style.height = `${this.options.height}px`;
|
|
288
|
+
// Render the waveform
|
|
289
|
+
this.renderPeaks(audioData, width, height, pixelRatio);
|
|
290
|
+
this.audioData = audioData;
|
|
291
|
+
this.emit('render');
|
|
217
292
|
}
|
|
218
|
-
|
|
293
|
+
reRender() {
|
|
294
|
+
// Return if the waveform has not been rendered yet
|
|
295
|
+
if (!this.audioData)
|
|
296
|
+
return;
|
|
219
297
|
// Remember the current cursor position
|
|
220
|
-
const oldCursorPosition = this.
|
|
221
|
-
|
|
222
|
-
this.render(audioData);
|
|
298
|
+
const oldCursorPosition = this.progressWrapper.clientWidth;
|
|
299
|
+
// Set the new zoom level and re-render the waveform
|
|
300
|
+
this.render(this.audioData);
|
|
223
301
|
// Adjust the scroll position so that the cursor stays in the same place
|
|
224
|
-
const newCursortPosition = this.
|
|
302
|
+
const newCursortPosition = this.progressWrapper.clientWidth;
|
|
225
303
|
this.scrollContainer.scrollLeft += newCursortPosition - oldCursorPosition;
|
|
226
304
|
}
|
|
227
|
-
|
|
228
|
-
this.
|
|
229
|
-
this.
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
305
|
+
zoom(minPxPerSec) {
|
|
306
|
+
this.options.minPxPerSec = minPxPerSec;
|
|
307
|
+
this.reRender();
|
|
308
|
+
}
|
|
309
|
+
scrollIntoView(progress, isPlaying = false) {
|
|
310
|
+
const { clientWidth, scrollLeft, scrollWidth } = this.scrollContainer;
|
|
311
|
+
const progressWidth = scrollWidth * progress;
|
|
312
|
+
const center = clientWidth / 2;
|
|
313
|
+
const minScroll = isPlaying && this.options.autoCenter && !this.isDragging ? center : clientWidth;
|
|
314
|
+
if (progressWidth > scrollLeft + minScroll || progressWidth < scrollLeft) {
|
|
315
|
+
// Scroll to the center
|
|
316
|
+
if (this.options.autoCenter && !this.isDragging) {
|
|
317
|
+
// If the cursor is in viewport but not centered, scroll to the center slowly
|
|
318
|
+
const minDiff = center / 20;
|
|
319
|
+
if (progressWidth - (scrollLeft + center) >= minDiff && progressWidth < scrollLeft + clientWidth) {
|
|
320
|
+
this.scrollContainer.scrollLeft += minDiff;
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
// Otherwise, scroll to the center immediately
|
|
324
|
+
this.scrollContainer.scrollLeft = progressWidth - center;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else if (this.isDragging) {
|
|
328
|
+
// Scroll just a little bit to allow for some space between the cursor and the edge
|
|
329
|
+
const gap = 10;
|
|
330
|
+
this.scrollContainer.scrollLeft =
|
|
331
|
+
progressWidth < scrollLeft ? progressWidth - gap : progressWidth - clientWidth + gap;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
// Scroll to the beginning
|
|
335
|
+
this.scrollContainer.scrollLeft = progressWidth;
|
|
235
336
|
}
|
|
236
337
|
}
|
|
338
|
+
// Emit the scroll event
|
|
339
|
+
{
|
|
340
|
+
const { scrollLeft } = this.scrollContainer;
|
|
341
|
+
const startX = scrollLeft / scrollWidth;
|
|
342
|
+
const endX = (scrollLeft + clientWidth) / scrollWidth;
|
|
343
|
+
this.emit('scroll', startX, endX);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
renderProgress(progress, isPlaying) {
|
|
347
|
+
if (isNaN(progress))
|
|
348
|
+
return;
|
|
349
|
+
this.progressWrapper.style.width = `${progress * 100}%`;
|
|
350
|
+
this.cursor.style.left = `${progress * 100}%`;
|
|
351
|
+
this.cursor.style.marginLeft = Math.round(progress * 100) === 100 ? `-${this.options.cursorWidth}px` : '';
|
|
352
|
+
if (this.isScrolling && this.options.autoScroll) {
|
|
353
|
+
this.scrollIntoView(progress, isPlaying);
|
|
354
|
+
}
|
|
237
355
|
}
|
|
238
356
|
}
|
|
357
|
+
Renderer.MAX_CANVAS_WIDTH = 4000;
|
|
239
358
|
export default Renderer;
|