wavesurfer.js 7.0.0-alpha.30 → 7.0.0-alpha.32
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/player.js +1 -2
- package/dist/plugins/envelope.d.ts +4 -4
- package/dist/plugins/envelope.js +19 -19
- package/dist/plugins/envelope.min.js +1 -1
- package/dist/plugins/minimap.min.js +1 -1
- package/dist/plugins/multitrack.js +40 -30
- package/dist/plugins/multitrack.min.js +1 -1
- package/dist/plugins/regions.d.ts +62 -40
- package/dist/plugins/regions.js +275 -218
- package/dist/plugins/regions.min.js +1 -1
- package/dist/plugins/spectrogram.d.ts +24 -0
- package/dist/plugins/spectrogram.js +165 -0
- package/dist/renderer.d.ts +5 -2
- package/dist/renderer.js +25 -21
- package/dist/wavesurfer.d.ts +1 -0
- package/dist/wavesurfer.js +13 -2
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +4 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Regions=t():e.Regions=t()}(WaveSurfer,(()=>(()=>{"use strict";var e={284:(e,t,
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Regions=t():e.Regions=t()}(WaveSurfer,(()=>(()=>{"use strict";var e={284:(e,t,n)=>{n.d(t,{Z:()=>r});var i=n(139);class s extends i.Z{wavesurfer;container;wrapper;subscriptions=[];options;constructor(e){super(),this.options=e}init(e){this.wavesurfer=e.wavesurfer,this.container=e.container,this.wrapper=e.wrapper}destroy(){this.subscriptions.forEach((e=>e()))}}const r=s},139:(e,t,n)=>{n.d(t,{Z:()=>i});const i=class{eventTarget;constructor(){this.eventTarget=new EventTarget}emit(e,t){const n=new CustomEvent(String(e),{detail:t});this.eventTarget.dispatchEvent(n)}on(e,t,n){const i=e=>{e instanceof CustomEvent&&t(e.detail)},s=String(e);return this.eventTarget.addEventListener(s,i,{once:n}),()=>this.eventTarget.removeEventListener(s,i)}once(e,t){return this.on(e,t,!0)}}}},t={};function n(i){var s=t[i];if(void 0!==s)return s.exports;var r=t[i]={exports:{}};return e[i](r,r.exports,n),r.exports}n.d=(e,t)=>{for(var i in t)n.o(t,i)&&!n.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var i={};return(()=>{n.d(i,{default:()=>a});var e=n(284),t=n(139);function s(e,t,n,i){if(!e)return;let s=!1;e.addEventListener("click",(e=>{s&&e.stopPropagation()})),e.addEventListener("mousedown",(e=>{e.stopPropagation();let r=e.clientX;t(r);const o=e=>{const t=e.clientX,i=t-r;r=t,s=!0,n(i)},a=()=>{i(),setTimeout((()=>s=!1),10),document.removeEventListener("mousemove",o),document.removeEventListener("mouseup",a)};document.addEventListener("mousemove",o),document.addEventListener("mouseup",a)}))}class r extends t.Z{totalDuration;element;id;start;end;drag;resize;color;content;constructor(e,t){super(),this.totalDuration=t,this.id=e.id||Math.random().toString(32).slice(2),this.start=e.start,this.end=e.end??e.start,this.drag=e.drag??!0,this.resize=e.resize??!0,this.color=e.color??"rgba(0, 0, 0, 0.1)",this.element=this.initElement(e.content),this.renderPosition(),this.initMouseEvents()}initElement(e){const t=document.createElement("div"),n=this.start===this.end;if(t.id=this.id,t.setAttribute("style",`\n position: absolute;\n height: 100%;\n background-color: ${n?"none":this.color};\n border-left: ${n?"2px solid "+this.color:"none"};\n border-radius: 2px;\n box-sizing: border-box;\n transition: background-color 0.2s ease;\n cursor: ${this.drag?"grab":"default"};\n pointer-events: all;\n padding: 0.2em ${n?.2:.4}em;\n pointer-events: all;\n `),e&&("string"==typeof e?(this.content=document.createElement("div"),this.content.textContent=e):this.content=e,t.appendChild(this.content)),!n){const e=document.createElement("div");e.setAttribute("data-resize","left"),e.setAttribute("style",`\n position: absolute;\n z-index: 2;\n width: 6px;\n height: 100%;\n top: 0;\n left: 0;\n border-left: 2px solid rgba(0, 0, 0, 0.5);\n border-radius: 2px 0 0 2px;\n cursor: ${this.resize?"ew-resize":"default"};\n word-break: keep-all;\n `);const n=e.cloneNode();n.setAttribute("data-resize","right"),n.style.left="",n.style.right="0",n.style.borderRight=n.style.borderLeft,n.style.borderLeft="",n.style.borderRadius="0 2px 2px 0",t.appendChild(e),t.appendChild(n)}return t}renderPosition(){const e=this.start/this.totalDuration,t=this.end/this.totalDuration;this.element.style.left=100*e+"%",this.element.style.width=100*(t-e)+"%"}initMouseEvents(){const{element:e}=this;e.addEventListener("mouseenter",(e=>this.emit("over",{event:e}))),e.addEventListener("mouseleave",(e=>this.emit("leave",{event:e}))),e.addEventListener("click",(e=>this.emit("click",{event:e}))),e.addEventListener("dblclick",(e=>this.emit("dblclick",{event:e}))),s(e,(()=>this.onStartMoving()),(e=>this.onMove(e)),(()=>this.onEndMoving())),s(e.querySelector('[data-resize="left"]'),(()=>null),(e=>this.onResize(e,"start")),(()=>this.onEndResizing())),s(e.querySelector('[data-resize="right"]'),(()=>null),(e=>this.onResize(e,"end")),(()=>this.onEndResizing()))}onStartMoving(){this.drag&&(this.element.style.cursor="grabbing")}onEndMoving(){this.drag&&(this.element.style.cursor="grab",this.emit("update-end"))}onUpdate(e,t){if(!this.element.parentElement)return;const n=e/this.element.parentElement.clientWidth*this.totalDuration;t.forEach((e=>{this[e]+=n,"start"===e?this.start=Math.max(0,Math.min(this.start,this.end)):this.end=Math.max(this.start,Math.min(this.end,this.totalDuration))})),this.renderPosition(),this.emit("update")}onMove(e){this.drag&&this.onUpdate(e,["start","end"])}onResize(e,t){this.resize&&this.onUpdate(e,[t])}onEndResizing(){this.resize&&this.emit("update-end")}play(){this.emit("play")}setTotalDuration(e){this.totalDuration=e,this.renderPosition()}remove(){this.emit("remove"),this.element.remove()}}class o extends e.Z{regions=[];regionsContainer;constructor(e){super(e),this.regionsContainer=this.initRegionsContainer()}static create(e){return new o(e)}init(e){if(super.init(e),!this.wavesurfer||!this.wrapper)throw Error("WaveSurfer is not initialized");this.wrapper.appendChild(this.regionsContainer)}initRegionsContainer(){const e=document.createElement("div");return e.setAttribute("style","\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 3;\n pointer-events: none;\n "),e}getRegions(){return this.regions}avoidOverlapping(e){if(!e.content)return;const t=e.content,n=t.getBoundingClientRect().left,i=e.element.scrollWidth,s=this.regions.filter((t=>{if(t===e||!t.content)return!1;const s=t.content.getBoundingClientRect().left,r=t.element.scrollWidth;return n<s+r&&s<n+i})).map((e=>e.content?.getBoundingClientRect().height||0)).reduce(((e,t)=>e+t),0);t.style.marginTop=`${s}px`}saveRegion(e){this.regionsContainer.appendChild(e.element),this.avoidOverlapping(e),this.regions.push(e),this.emit("region-created",{region:e}),this.subscriptions.push(e.on("update-end",(()=>{this.avoidOverlapping(e),this.emit("region-updated",{region:e})})),e.on("play",(()=>{this.wavesurfer?.play(),this.wavesurfer?.setTime(e.start)})),e.on("click",(()=>{this.emit("region-clicked",{region:e})})))}addRegion(e){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");const t=this.wavesurfer.getDuration(),n=new r(e,t);return t?this.saveRegion(n):this.subscriptions.push(this.wavesurfer.once("canplay",(({duration:e})=>{n.setTotalDuration(e),this.saveRegion(n)}))),n}add(e,t,n,i){return this.addRegion({start:e,end:t,content:n,color:i})}enableDragSelection(e){if(!this.wrapper)return;let t=null,n=0,i=0;s(this.wrapper,(e=>n=e),(s=>{if(i+=s,this.wavesurfer&&this.wrapper){if(!t&&i>5){const s=this.wavesurfer.getDuration(),o=this.wrapper.getBoundingClientRect(),a=(n+i-o.left)/o.width*s;t=new r({...e,start:a,end:a},s),this.regionsContainer.appendChild(t.element)}t&&t.onUpdate(s,["end"])}}),(()=>t&&this.saveRegion(t)))}clearRegions(){this.regions.forEach((e=>e.remove()))}destroy(){this.clearRegions(),super.destroy()}}const a=o})(),i.default})()));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import BasePlugin from '../base-plugin.js';
|
|
2
|
+
export type SpectrogramPluginOptions = {
|
|
3
|
+
waveColor?: string;
|
|
4
|
+
lineWidth?: number;
|
|
5
|
+
};
|
|
6
|
+
export type SpectrogramPluginEvents = {
|
|
7
|
+
startRecording: void;
|
|
8
|
+
stopRecording: void;
|
|
9
|
+
};
|
|
10
|
+
declare class SpectrogramPlugin extends BasePlugin<SpectrogramPluginEvents, SpectrogramPluginOptions> {
|
|
11
|
+
private mediaSpectrogramer;
|
|
12
|
+
private recordedUrl;
|
|
13
|
+
static create(options?: SpectrogramPluginOptions): SpectrogramPlugin;
|
|
14
|
+
private loadBlob;
|
|
15
|
+
private processFrequencyData;
|
|
16
|
+
render(stream: MediaStream): () => void;
|
|
17
|
+
private cleanUp;
|
|
18
|
+
startSpectrograming(): Promise<void>;
|
|
19
|
+
isSpectrograming(): boolean;
|
|
20
|
+
stopSpectrograming(): void;
|
|
21
|
+
getSpectrogramedUrl(): string;
|
|
22
|
+
destroy(): void;
|
|
23
|
+
}
|
|
24
|
+
export default SpectrogramPlugin;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import BasePlugin from '../base-plugin.js';
|
|
2
|
+
class SpectrogramPlugin extends BasePlugin {
|
|
3
|
+
mediaSpectrogramer = null;
|
|
4
|
+
recordedUrl = '';
|
|
5
|
+
static create(options) {
|
|
6
|
+
return new SpectrogramPlugin(options || {});
|
|
7
|
+
}
|
|
8
|
+
loadBlob(data) {
|
|
9
|
+
const blob = new Blob(data, { type: 'audio/webm' });
|
|
10
|
+
this.recordedUrl = URL.createObjectURL(blob);
|
|
11
|
+
this.wavesurfer?.load(this.recordedUrl);
|
|
12
|
+
}
|
|
13
|
+
// Function to process frequency data
|
|
14
|
+
processFrequencyData(dataArray, sampleRate) {
|
|
15
|
+
// Smooth the dataArray using a moving average
|
|
16
|
+
const windowSize = 5; // Adjust the window size as needed
|
|
17
|
+
const smoothedDataArray = new Float32Array(dataArray.length);
|
|
18
|
+
for (let i = 0; i < dataArray.length; i++) {
|
|
19
|
+
let sum = 0;
|
|
20
|
+
let count = 0;
|
|
21
|
+
for (let j = i - Math.floor(windowSize / 2); j <= i + Math.floor(windowSize / 2); j++) {
|
|
22
|
+
if (j >= 0 && j < dataArray.length) {
|
|
23
|
+
sum += dataArray[j];
|
|
24
|
+
count++;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
smoothedDataArray[i] = sum / count;
|
|
28
|
+
}
|
|
29
|
+
// Find the first two peaks (formants) above a certain threshold
|
|
30
|
+
const threshold = -70; // Adjust the threshold as needed
|
|
31
|
+
const formants = [];
|
|
32
|
+
let prevValue = smoothedDataArray[0];
|
|
33
|
+
let prevSlope = 0;
|
|
34
|
+
for (let i = 1; i < smoothedDataArray.length - 1; i++) {
|
|
35
|
+
const slope = smoothedDataArray[i] - prevValue;
|
|
36
|
+
if (slope > 0 && prevSlope < 0 && smoothedDataArray[i] > threshold) {
|
|
37
|
+
formants.push(i);
|
|
38
|
+
if (formants.length >= 2) {
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
prevValue = smoothedDataArray[i];
|
|
43
|
+
prevSlope = slope;
|
|
44
|
+
}
|
|
45
|
+
// Convert the formant indices to frequencies
|
|
46
|
+
const nyquist = 0.5 * sampleRate;
|
|
47
|
+
const freqStep = nyquist / dataArray.length;
|
|
48
|
+
const formantFrequencies = formants.map(index => index * freqStep);
|
|
49
|
+
// Print the formant frequencies
|
|
50
|
+
if (formantFrequencies.length >= 2) {
|
|
51
|
+
return formantFrequencies;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
render(stream) {
|
|
55
|
+
if (!this.container || !this.wavesurfer)
|
|
56
|
+
return () => undefined;
|
|
57
|
+
const canvas = document.createElement('canvas');
|
|
58
|
+
canvas.width = this.container.clientWidth;
|
|
59
|
+
canvas.height = this.container.clientHeight;
|
|
60
|
+
canvas.style.zIndex = '10';
|
|
61
|
+
this.container.appendChild(canvas);
|
|
62
|
+
const canvasCtx = canvas.getContext('2d');
|
|
63
|
+
const audioContext = new AudioContext();
|
|
64
|
+
const source = audioContext.createMediaStreamSource(stream);
|
|
65
|
+
const analyser = audioContext.createAnalyser();
|
|
66
|
+
source.connect(analyser);
|
|
67
|
+
// An array that will store real-time waveform data
|
|
68
|
+
const bufferLength = analyser.frequencyBinCount;
|
|
69
|
+
const dataArray = new Uint8Array(bufferLength);
|
|
70
|
+
// An array that will store FFT data
|
|
71
|
+
const freqArray = new Float32Array(bufferLength);
|
|
72
|
+
let animationId;
|
|
73
|
+
const drawWaveform = () => {
|
|
74
|
+
if (!canvasCtx)
|
|
75
|
+
return;
|
|
76
|
+
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
|
|
77
|
+
analyser.getByteTimeDomainData(dataArray);
|
|
78
|
+
analyser.getFloatFrequencyData(freqArray);
|
|
79
|
+
this.processFrequencyData(freqArray, audioContext.sampleRate);
|
|
80
|
+
canvasCtx.lineWidth = this.options.lineWidth || 2;
|
|
81
|
+
canvasCtx.strokeStyle = this.options.waveColor || this.wavesurfer?.options.waveColor || '';
|
|
82
|
+
canvasCtx.beginPath();
|
|
83
|
+
const sliceWidth = (canvas.width * 1.0) / bufferLength;
|
|
84
|
+
let x = 0;
|
|
85
|
+
for (let i = 0; i < bufferLength; i++) {
|
|
86
|
+
const v = dataArray[i] / 128.0;
|
|
87
|
+
const y = (v * canvas.height) / 2;
|
|
88
|
+
if (i === 0) {
|
|
89
|
+
canvasCtx.moveTo(x, y);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
canvasCtx.lineTo(x, y);
|
|
93
|
+
}
|
|
94
|
+
x += sliceWidth;
|
|
95
|
+
}
|
|
96
|
+
canvasCtx.lineTo(canvas.width, canvas.height / 2);
|
|
97
|
+
canvasCtx.stroke();
|
|
98
|
+
animationId = requestAnimationFrame(drawWaveform);
|
|
99
|
+
};
|
|
100
|
+
drawWaveform();
|
|
101
|
+
return () => {
|
|
102
|
+
if (animationId) {
|
|
103
|
+
cancelAnimationFrame(animationId);
|
|
104
|
+
}
|
|
105
|
+
if (source) {
|
|
106
|
+
source.disconnect();
|
|
107
|
+
source.mediaStream.getTracks().forEach((track) => track.stop());
|
|
108
|
+
}
|
|
109
|
+
if (audioContext) {
|
|
110
|
+
audioContext.close();
|
|
111
|
+
}
|
|
112
|
+
canvas?.remove();
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
cleanUp() {
|
|
116
|
+
this.stopSpectrograming();
|
|
117
|
+
this.wavesurfer?.empty();
|
|
118
|
+
if (this.recordedUrl) {
|
|
119
|
+
URL.revokeObjectURL(this.recordedUrl);
|
|
120
|
+
this.recordedUrl = '';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async startSpectrograming() {
|
|
124
|
+
this.cleanUp();
|
|
125
|
+
let stream;
|
|
126
|
+
try {
|
|
127
|
+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
throw new Error('Error accessing the microphone: ' + err.message);
|
|
131
|
+
}
|
|
132
|
+
const onStop = this.render(stream);
|
|
133
|
+
const mediaSpectrogramer = new MediaSpectrogramer(stream);
|
|
134
|
+
const recordedChunks = [];
|
|
135
|
+
mediaSpectrogramer.addEventListener('dataavailable', (event) => {
|
|
136
|
+
if (event.data.size > 0) {
|
|
137
|
+
recordedChunks.push(event.data);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
mediaSpectrogramer.addEventListener('stop', () => {
|
|
141
|
+
onStop();
|
|
142
|
+
this.loadBlob(recordedChunks);
|
|
143
|
+
this.emit('stopSpectrograming');
|
|
144
|
+
});
|
|
145
|
+
mediaSpectrogramer.start();
|
|
146
|
+
this.emit('startSpectrograming');
|
|
147
|
+
this.mediaSpectrogramer = mediaSpectrogramer;
|
|
148
|
+
}
|
|
149
|
+
isSpectrograming() {
|
|
150
|
+
return this.mediaSpectrogramer?.state === 'recording';
|
|
151
|
+
}
|
|
152
|
+
stopSpectrograming() {
|
|
153
|
+
if (this.isSpectrograming()) {
|
|
154
|
+
this.mediaSpectrogramer?.stop();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
getSpectrogramedUrl() {
|
|
158
|
+
return this.recordedUrl;
|
|
159
|
+
}
|
|
160
|
+
destroy() {
|
|
161
|
+
super.destroy();
|
|
162
|
+
this.cleanUp();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
export default SpectrogramPlugin;
|
package/dist/renderer.d.ts
CHANGED
|
@@ -21,7 +21,9 @@ type RendererEvents = {
|
|
|
21
21
|
relativeX: number;
|
|
22
22
|
};
|
|
23
23
|
};
|
|
24
|
+
type ChannelData = Float32Array[] | Array<number[]>;
|
|
24
25
|
declare class Renderer extends EventEmitter<RendererEvents> {
|
|
26
|
+
private static MAX_CANVAS_WIDTH;
|
|
25
27
|
private options;
|
|
26
28
|
private container;
|
|
27
29
|
private scrollContainer;
|
|
@@ -30,7 +32,8 @@ declare class Renderer extends EventEmitter<RendererEvents> {
|
|
|
30
32
|
private progressWrapper;
|
|
31
33
|
private timeout;
|
|
32
34
|
private isScrolling;
|
|
33
|
-
private
|
|
35
|
+
private channelData;
|
|
36
|
+
private duration;
|
|
34
37
|
private resizeObserver;
|
|
35
38
|
constructor(params: RendererRequiredParams, options: RendererStyleOptions);
|
|
36
39
|
private initHtml;
|
|
@@ -40,7 +43,7 @@ declare class Renderer extends EventEmitter<RendererEvents> {
|
|
|
40
43
|
destroy(): void;
|
|
41
44
|
private delay;
|
|
42
45
|
private renderPeaks;
|
|
43
|
-
render(
|
|
46
|
+
render(channelData: ChannelData, duration: number): void;
|
|
44
47
|
reRender(): void;
|
|
45
48
|
zoom(minPxPerSec: number): void;
|
|
46
49
|
renderProgress(progress: number, autoCenter?: boolean): void;
|
package/dist/renderer.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import EventEmitter from './event-emitter.js';
|
|
2
2
|
class Renderer extends EventEmitter {
|
|
3
|
+
static MAX_CANVAS_WIDTH = 4000;
|
|
3
4
|
options = {
|
|
4
5
|
height: 0,
|
|
5
6
|
};
|
|
@@ -10,7 +11,8 @@ class Renderer extends EventEmitter {
|
|
|
10
11
|
progressWrapper;
|
|
11
12
|
timeout = null;
|
|
12
13
|
isScrolling = false;
|
|
13
|
-
|
|
14
|
+
channelData = null;
|
|
15
|
+
duration = null;
|
|
14
16
|
resizeObserver = null;
|
|
15
17
|
constructor(params, options) {
|
|
16
18
|
super();
|
|
@@ -61,13 +63,15 @@ class Renderer extends EventEmitter {
|
|
|
61
63
|
}
|
|
62
64
|
:host .noScrollbar {
|
|
63
65
|
scrollbar-color: transparent;
|
|
66
|
+
scrollbar-width: none;
|
|
64
67
|
}
|
|
65
68
|
:host .noScrollbar::-webkit-scrollbar {
|
|
66
69
|
display: none;
|
|
70
|
+
-webkit-appearance: none;
|
|
67
71
|
}
|
|
68
72
|
:host .wrapper {
|
|
69
73
|
position: relative;
|
|
70
|
-
|
|
74
|
+
overflow: visible;
|
|
71
75
|
z-index: 2;
|
|
72
76
|
}
|
|
73
77
|
:host .canvases {
|
|
@@ -105,9 +109,8 @@ class Renderer extends EventEmitter {
|
|
|
105
109
|
}
|
|
106
110
|
setOptions(options) {
|
|
107
111
|
this.options = options;
|
|
108
|
-
// Re-render the waveform
|
|
109
|
-
|
|
110
|
-
this.reRender();
|
|
112
|
+
// Re-render the waveform
|
|
113
|
+
this.reRender();
|
|
111
114
|
}
|
|
112
115
|
getContainer() {
|
|
113
116
|
return this.scrollContainer;
|
|
@@ -204,8 +207,9 @@ class Renderer extends EventEmitter {
|
|
|
204
207
|
// Determine the currently visible part of the waveform
|
|
205
208
|
const { scrollLeft, scrollWidth, clientWidth } = this.scrollContainer;
|
|
206
209
|
const scale = len / scrollWidth;
|
|
210
|
+
const viewportWidth = Math.min(Renderer.MAX_CANVAS_WIDTH, scrollWidth, scrollLeft + clientWidth);
|
|
207
211
|
const start = Math.floor(scrollLeft * scale);
|
|
208
|
-
const end = Math.ceil(
|
|
212
|
+
const end = Math.ceil(viewportWidth * scale);
|
|
209
213
|
// Draw the visible portion of the waveform
|
|
210
214
|
draw(start, end);
|
|
211
215
|
// Draw the rest of the waveform with a timeout for better performance
|
|
@@ -221,16 +225,19 @@ class Renderer extends EventEmitter {
|
|
|
221
225
|
});
|
|
222
226
|
}
|
|
223
227
|
}
|
|
224
|
-
render(
|
|
228
|
+
render(channelData, duration) {
|
|
225
229
|
// Determine the width of the waveform
|
|
226
230
|
const pixelRatio = window.devicePixelRatio || 1;
|
|
227
|
-
const parentWidth = this.
|
|
228
|
-
const scrollWidth =
|
|
229
|
-
|
|
231
|
+
const parentWidth = this.scrollContainer.clientWidth;
|
|
232
|
+
const scrollWidth = Math.ceil(duration * (this.options.minPxPerSec || 0));
|
|
233
|
+
// Whether the container should scroll
|
|
234
|
+
this.isScrolling = scrollWidth > parentWidth;
|
|
235
|
+
const useParentWidth = this.options.fillParent && !this.isScrolling;
|
|
236
|
+
// Width and height of the waveform in pixels
|
|
237
|
+
const width = (useParentWidth ? parentWidth : scrollWidth) * pixelRatio;
|
|
230
238
|
const { height } = this.options;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
this.wrapper.style.width = `${Math.floor(width / pixelRatio)}px`;
|
|
239
|
+
// Set the width of the wrapper
|
|
240
|
+
this.wrapper.style.width = useParentWidth ? '100%' : `${scrollWidth}px`;
|
|
234
241
|
// Set additional styles
|
|
235
242
|
this.scrollContainer.style.overflowX = this.isScrolling ? 'auto' : 'hidden';
|
|
236
243
|
this.scrollContainer.classList.toggle('noScrollbar', !!this.options.hideScrollbar);
|
|
@@ -238,22 +245,19 @@ class Renderer extends EventEmitter {
|
|
|
238
245
|
this.progressWrapper.style.borderRightColor = `${this.options.cursorColor || this.options.progressColor}`;
|
|
239
246
|
this.progressWrapper.style.borderRightWidth = `${this.options.cursorWidth}px`;
|
|
240
247
|
this.canvasWrapper.style.height = `${this.options.height}px`;
|
|
241
|
-
// Only the first two channels are used
|
|
242
|
-
const channelData = [audioData.getChannelData(0)];
|
|
243
|
-
if (audioData.numberOfChannels > 1) {
|
|
244
|
-
channelData.push(audioData.getChannelData(1));
|
|
245
|
-
}
|
|
246
248
|
// Render the waveform
|
|
247
249
|
this.renderPeaks(channelData, width, height, pixelRatio);
|
|
248
|
-
this.
|
|
250
|
+
this.channelData = channelData;
|
|
251
|
+
this.duration = duration;
|
|
249
252
|
}
|
|
250
253
|
reRender() {
|
|
251
|
-
if
|
|
254
|
+
// Return if the waveform has not been rendered yet
|
|
255
|
+
if (!this.channelData || !this.duration)
|
|
252
256
|
return;
|
|
253
257
|
// Remember the current cursor position
|
|
254
258
|
const oldCursorPosition = this.progressWrapper.clientWidth;
|
|
255
259
|
// Set the new zoom level and re-render the waveform
|
|
256
|
-
this.render(this.
|
|
260
|
+
this.render(this.channelData, this.duration);
|
|
257
261
|
// Adjust the scroll position so that the cursor stays in the same place
|
|
258
262
|
const newCursortPosition = this.progressWrapper.clientWidth;
|
|
259
263
|
this.scrollContainer.scrollLeft += newCursortPosition - oldCursorPosition;
|
package/dist/wavesurfer.d.ts
CHANGED
|
@@ -108,6 +108,7 @@ declare class WaveSurfer extends Player<WaveSurferEvents> {
|
|
|
108
108
|
registerPlugin<T extends GenericPlugin>(plugin: T): T;
|
|
109
109
|
/** Load an audio file by URL, with optional pre-decoded audio data */
|
|
110
110
|
load(url: string, channelData?: WaveSurferOptions['peaks'], duration?: number): Promise<void>;
|
|
111
|
+
private renderAudio;
|
|
111
112
|
/** Zoom in or out */
|
|
112
113
|
zoom(minPxPerSec: number): void;
|
|
113
114
|
/** Get the decoded audio data */
|
package/dist/wavesurfer.js
CHANGED
|
@@ -145,8 +145,19 @@ class WaveSurfer extends Player {
|
|
|
145
145
|
getChannelData: (i) => channelData?.[i],
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
|
-
this.
|
|
149
|
-
this.emit('decode', { duration: this.
|
|
148
|
+
this.renderAudio();
|
|
149
|
+
this.emit('decode', { duration: this.getDuration() });
|
|
150
|
+
}
|
|
151
|
+
renderAudio() {
|
|
152
|
+
if (!this.decodedData)
|
|
153
|
+
return;
|
|
154
|
+
// Only the first two channels are used
|
|
155
|
+
const channelData = [this.decodedData.getChannelData(0)];
|
|
156
|
+
if (this.decodedData.numberOfChannels > 1) {
|
|
157
|
+
channelData.push(this.decodedData.getChannelData(1));
|
|
158
|
+
}
|
|
159
|
+
const duration = this.getDuration();
|
|
160
|
+
this.renderer.render(channelData, duration);
|
|
150
161
|
}
|
|
151
162
|
/** Zoom in or out */
|
|
152
163
|
zoom(minPxPerSec) {
|
package/dist/wavesurfer.min.js
CHANGED
|
@@ -1 +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.WaveSurfer=e():t.WaveSurfer=e()}(this,(()=>(()=>{"use strict";var t={d:(e,i)=>{for(var s in i)t.o(i,s)&&!t.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:i[s]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>h});const i=class{eventTarget;constructor(){this.eventTarget=new EventTarget}emit(t,e){const i=new CustomEvent(String(t),{detail:e});this.eventTarget.dispatchEvent(i)}on(t,e,i){const s=t=>{t instanceof CustomEvent&&e(t.detail)},n=String(t);return this.eventTarget.addEventListener(n,s,{once:i}),()=>this.eventTarget.removeEventListener(n,s)}once(t,e){return this.on(t,e,!0)}},s=class extends i{options={height:0};container;scrollContainer;wrapper;canvasWrapper;progressWrapper;timeout=null;isScrolling=!1;audioData=null;resizeObserver=null;constructor(t,e){super(),this.options={...e};let i=null;if("string"==typeof t.container?i=document.querySelector(t.container):t.container instanceof HTMLElement&&(i=t.container),!i)throw new Error("Container not found");const[s,n]=this.initHtml();i.appendChild(s),this.container=s,this.scrollContainer=n.querySelector(".scroll"),this.wrapper=n.querySelector(".wrapper"),this.canvasWrapper=n.querySelector(".canvases"),this.progressWrapper=n.querySelector(".progress"),this.wrapper.addEventListener("click",(t=>{const e=this.wrapper.getBoundingClientRect(),i=(t.clientX-e.left)/e.width;this.emit("click",{relativeX:i})})),this.resizeObserver=new ResizeObserver((()=>{this.delay((()=>this.reRender()),100)})),this.resizeObserver.observe(this.scrollContainer)}initHtml(){const t=document.createElement("div"),e=t.attachShadow({mode:"open"});return e.innerHTML=`\n <style>\n :host {\n user-select: none;\n }\n :host .scroll {\n overflow-x: auto;\n overflow-y: hidden;\n width: 100%;\n position: relative;\n }\n :host .noScrollbar {\n scrollbar-color: transparent;\n }\n :host .noScrollbar::-webkit-scrollbar {\n display: none;\n }\n :host .wrapper {\n position: relative;\n min-width: 100%;\n z-index: 2;\n }\n :host .canvases {\n position: relative;\n height: ${this.options.height}px;\n }\n :host canvas {\n display: block;\n position: absolute;\n top: 0;\n image-rendering: pixelated;\n height: ${this.options.height}px;\n }\n :host .progress {\n pointer-events: none;\n position: absolute;\n z-index: 2;\n top: 0;\n left: 0;\n width: 0;\n height: 100%;\n overflow: hidden;\n box-sizing: border-box;\n }\n </style>\n\n <div class="scroll">\n <div class="wrapper">\n <div class="canvases"></div>\n <div class="progress"></div>\n </div>\n </div>\n `,[t,e]}setOptions(t){this.options=t,this.audioData&&this.reRender()}getContainer(){return this.scrollContainer}getWrapper(){return this.wrapper}destroy(){this.container.remove(),this.resizeObserver?.disconnect()}delay(t,e=10){return this.timeout&&clearTimeout(this.timeout),new Promise((i=>{this.timeout=setTimeout((()=>{i(t())}),e)}))}async renderPeaks(t,e,i,s){const n=null!=this.options.barWidth?this.options.barWidth*s:1,r=null!=this.options.barGap?this.options.barGap*s:this.options.barWidth?n/2:0,o=this.options.barRadius??0,a=t[0],h=a.length,d=Math.floor(e/(n+r))/h,l=i/2,c=1===t.length,p=c?a:t[1],u=c&&p.some((t=>t<0)),m=(t,i)=>{let c=0,m=0,g=0;const y=document.createElement("canvas");y.width=Math.round(e*(i-t)/h),y.height=this.options.height,y.style.width=`${Math.floor(y.width/s)}px`,y.style.height=`${this.options.height}px`,y.style.left=`${Math.floor(t*e/s/h)}px`,this.canvasWrapper.appendChild(y);const v=y.getContext("2d",{desynchronized:!0});v.beginPath(),v.fillStyle=this.options.waveColor??"",v.roundRect||(v.roundRect=v.fillRect);for(let e=t;e<i;e++){const i=Math.round((e-t)*d);if(i>c){const t=Math.round(m*l),e=Math.round(g*l);v.roundRect(c*(n+r),l-t,n,t+e||1,o),c=i,m=0,g=0}const s=u?a[e]:Math.abs(a[e]),h=u?p[e]:Math.abs(p[e]);s>m&&(m=s),(u?h<-g:h>g)&&(g=h<0?-h:h)}v.fill(),v.closePath();const f=y.cloneNode();this.progressWrapper.appendChild(f);const b=f.getContext("2d",{desynchronized:!0});y.width>0&&y.height>0&&b.drawImage(y,0,0),b.globalCompositeOperation="source-in",b.fillStyle=this.options.progressColor??"",b.fillRect(0,0,y.width,y.height)};this.canvasWrapper.innerHTML="",this.progressWrapper.innerHTML="";const{scrollLeft:g,scrollWidth:y,clientWidth:v}=this.scrollContainer,f=h/y,b=Math.floor(g*f),w=Math.ceil(Math.min(y,g+v)*f);m(b,w);const C=w-b;for(let t=w;t<h;t+=C)await this.delay((()=>{m(t,Math.min(h,t+C))}));for(let t=b-1;t>=0;t-=C)await this.delay((()=>{m(Math.max(0,t-C),t)}))}render(t){const e=window.devicePixelRatio||1,i=this.options.fillParent?this.scrollContainer.clientWidth*e:0,s=this.options.minPxPerSec?Math.max(1,t.duration*this.options.minPxPerSec):0,n=s>i?s:i,{height:r}=this.options;this.isScrolling=n>i,this.wrapper.style.width=`${Math.floor(n/e)}px`,this.scrollContainer.style.overflowX=this.isScrolling?"auto":"hidden",this.scrollContainer.classList.toggle("noScrollbar",!!this.options.hideScrollbar),this.progressWrapper.style.borderRightStyle="solid",this.progressWrapper.style.borderRightColor=`${this.options.cursorColor||this.options.progressColor}`,this.progressWrapper.style.borderRightWidth=`${this.options.cursorWidth}px`,this.canvasWrapper.style.height=`${this.options.height}px`;const o=[t.getChannelData(0)];t.numberOfChannels>1&&o.push(t.getChannelData(1)),this.renderPeaks(o,n,r,e),this.audioData=t}reRender(){if(!this.audioData)return;const t=this.progressWrapper.clientWidth;this.render(this.audioData);const e=this.progressWrapper.clientWidth;this.scrollContainer.scrollLeft+=e-t}zoom(t){this.options.minPxPerSec=t,this.reRender()}renderProgress(t,e=!1){if(this.progressWrapper.style.width=100*t+"%",this.isScrolling&&this.options.autoCenter){const{clientWidth:i,scrollLeft:s,scrollWidth:n}=this.scrollContainer,r=n*t,o=i/2,a=o/20;(r>s+(e?o:i)||r<s)&&(r-(s+o)>=a&&r<s+i?this.scrollContainer.scrollLeft+=a:this.scrollContainer.scrollLeft=r-o)}}},n=class extends i{media;subscriptions=[];isExternalMedia=!1;hasPlayedOnce=!1;constructor(t){super(),t.media?(this.media=t.media,this.isExternalMedia=!0):this.media=document.createElement("audio"),this.subscriptions.push(this.onceMediaEvent("play",(()=>{this.hasPlayedOnce=!0}))),t.autoplay&&(this.media.autoplay=!0),null!=t.playbackRate&&(this.media.playbackRate=t.playbackRate)}onMediaEvent(t,e,i){return this.media.addEventListener(t,e,i),()=>this.media.removeEventListener(t,e)}onceMediaEvent(t,e){return this.onMediaEvent(t,e,{once:!0})}loadUrl(t){this.media.src=t}destroy(){this.media.pause(),this.subscriptions.forEach((t=>t())),this.isExternalMedia||this.media.remove()}play(){return this.media.play()}pause(){this.media.pause()}isPlaying(){return this.media.currentTime>0&&!this.media.paused&&!this.media.ended}setTime(t){!this.hasPlayedOnce&&navigator.userAgent.match(/(iPad|iPhone|iPod)/g)&&this.media.play()?.then?.((()=>{setTimeout((()=>this.media.pause()),10)})),this.media.currentTime=t}getDuration(){return this.media.duration}getCurrentTime(){return this.media.currentTime}getVolume(){return this.media.volume}setVolume(t){this.media.volume=t}getMuted(){return this.media.muted}setMuted(t){this.media.muted=t}getPlaybackRate(){return this.media.playbackRate}setPlaybackRate(t,e){null!=e&&(this.media.preservesPitch=e),this.media.playbackRate=t}getMediaElement(){return this.media}setSinkId(t){return this.media.setSinkId(t)}},r=class extends i{unsubscribe=()=>{};start(){this.unsubscribe=this.on("tick",(()=>{requestAnimationFrame((()=>{this.emit("tick")}))})),this.emit("tick")}stop(){this.unsubscribe()}destroy(){this.unsubscribe()}},o={height:128,waveColor:"#999",progressColor:"#555",cursorWidth:1,minPxPerSec:0,fillParent:!0,interact:!0,autoCenter:!0};class a extends n{options;fetcher;decoder;renderer;timer;plugins=[];decodedData=null;canPlay=!1;static create(t){return new a(t)}constructor(t){super({media:t.media,autoplay:t.autoplay,playbackRate:t.audioRate}),this.options=Object.assign({},o,t),this.fetcher=new class{async load(t){return fetch(t).then((t=>t.arrayBuffer()))}},this.decoder=new class{audioCtx=null;initAudioContext(t){this.audioCtx=new AudioContext({latencyHint:"playback",sampleRate:t})}constructor(){try{this.initAudioContext(3e3)}catch(t){this.initAudioContext(8e3)}}async decode(t){if(!this.audioCtx)throw new Error("AudioContext is not initialized");return this.audioCtx.decodeAudioData(t)}destroy(){this.audioCtx?.close(),this.audioCtx=null}},this.timer=new r,this.renderer=new s({container:this.options.container},this.options),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents(),this.initReadyEvent(),this.initPlugins();const e=this.options.url||this.options.media?.src;e&&this.load(e,this.options.peaks,this.options.duration)}setOptions(t){this.options={...this.options,...t},this.renderer.setOptions(this.options)}initPlayerEvents(){this.subscriptions.push(this.onMediaEvent("timeupdate",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),this.isPlaying()),this.emit("timeupdate",{currentTime:t})})),this.onMediaEvent("play",(()=>{this.emit("play"),this.timer.start()})),this.onMediaEvent("pause",(()=>{this.emit("pause"),this.timer.stop(),this.getCurrentTime()>=this.getDuration()&&this.emit("finish")})),this.onMediaEvent("canplay",(()=>{this.canPlay=!0,this.emit("canplay",{duration:this.getDuration()})})),this.onMediaEvent("seeking",(()=>{this.emit("seeking",{currentTime:this.getCurrentTime()})})))}initRendererEvents(){this.subscriptions.push(this.renderer.on("click",(({relativeX:t})=>{this.options.interact&&(this.seekTo(t),this.emit("interaction"))})))}initTimerEvents(){this.subscriptions.push(this.timer.on("tick",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),!0),this.emit("timeupdate",{currentTime:t})})))}initReadyEvent(){const t=()=>{this.decodedData&&this.canPlay&&this.emit("ready",{duration:this.getDuration()})};this.subscriptions.push(this.on("decode",t),this.on("canplay",t))}initPlugins(){this.options.plugins?.length&&this.options.plugins.forEach((t=>{this.registerPlugin(t)}))}registerPlugin(t){return t.init({wavesurfer:this,container:this.renderer.getContainer(),wrapper:this.renderer.getWrapper()}),this.plugins.push(t),t}async load(t,e,i){if(this.decodedData=null,this.canPlay=!1,this.loadUrl(t),null==e){const e=await this.fetcher.load(t),i=await this.decoder.decode(e);this.decodedData=i}else i||(i=await new Promise((t=>{this.onceMediaEvent("loadedmetadata",(()=>t(this.getDuration())))}))||0),"number"==typeof e[0]&&(e=[e]),this.decodedData={duration:i,numberOfChannels:e.length,sampleRate:e[0].length/i,getChannelData:t=>e?.[t]};this.renderer.render(this.decodedData),this.emit("decode",{duration:this.decodedData.duration})}zoom(t){if(!this.decodedData)throw new Error("No audio loaded");this.renderer.zoom(t),this.emit("zoom",{minPxPerSec:t})}getDecodedData(){return this.decodedData}getDuration(){const t=this.getMediaElement()?.duration;return t>0&&t<1/0?t:this.decodedData?.duration||0}toggleInteraction(t){this.options.interact=t}seekTo(t){const e=this.getDuration()*t;this.setTime(e)}playPause(){return this.isPlaying()?(this.pause(),Promise.resolve()):this.play()}stop(){this.pause(),this.setTime(0)}skip(t){this.setTime(this.getCurrentTime()+t)}empty(){this.load("",[[0]],.001)}destroy(){this.emit("destroy"),this.plugins.forEach((t=>t.destroy())),this.timer.destroy(),this.decoder.destroy(),this.renderer.destroy(),super.destroy()}}const h=a;return e.default})()));
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.WaveSurfer=e():t.WaveSurfer=e()}(this,(()=>(()=>{"use strict";var t={d:(e,i)=>{for(var s in i)t.o(i,s)&&!t.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:i[s]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>d});const i=class{eventTarget;constructor(){this.eventTarget=new EventTarget}emit(t,e){const i=new CustomEvent(String(t),{detail:e});this.eventTarget.dispatchEvent(i)}on(t,e,i){const s=t=>{t instanceof CustomEvent&&e(t.detail)},n=String(t);return this.eventTarget.addEventListener(n,s,{once:i}),()=>this.eventTarget.removeEventListener(n,s)}once(t,e){return this.on(t,e,!0)}};class s extends i{static MAX_CANVAS_WIDTH=4e3;options={height:0};container;scrollContainer;wrapper;canvasWrapper;progressWrapper;timeout=null;isScrolling=!1;channelData=null;duration=null;resizeObserver=null;constructor(t,e){super(),this.options={...e};let i=null;if("string"==typeof t.container?i=document.querySelector(t.container):t.container instanceof HTMLElement&&(i=t.container),!i)throw new Error("Container not found");const[s,n]=this.initHtml();i.appendChild(s),this.container=s,this.scrollContainer=n.querySelector(".scroll"),this.wrapper=n.querySelector(".wrapper"),this.canvasWrapper=n.querySelector(".canvases"),this.progressWrapper=n.querySelector(".progress"),this.wrapper.addEventListener("click",(t=>{const e=this.wrapper.getBoundingClientRect(),i=(t.clientX-e.left)/e.width;this.emit("click",{relativeX:i})})),this.resizeObserver=new ResizeObserver((()=>{this.delay((()=>this.reRender()),100)})),this.resizeObserver.observe(this.scrollContainer)}initHtml(){const t=document.createElement("div"),e=t.attachShadow({mode:"open"});return e.innerHTML=`\n <style>\n :host {\n user-select: none;\n }\n :host .scroll {\n overflow-x: auto;\n overflow-y: hidden;\n width: 100%;\n position: relative;\n }\n :host .noScrollbar {\n scrollbar-color: transparent;\n scrollbar-width: none;\n }\n :host .noScrollbar::-webkit-scrollbar {\n display: none;\n -webkit-appearance: none;\n }\n :host .wrapper {\n position: relative;\n overflow: visible;\n z-index: 2;\n }\n :host .canvases {\n position: relative;\n height: ${this.options.height}px;\n }\n :host canvas {\n display: block;\n position: absolute;\n top: 0;\n image-rendering: pixelated;\n height: ${this.options.height}px;\n }\n :host .progress {\n pointer-events: none;\n position: absolute;\n z-index: 2;\n top: 0;\n left: 0;\n width: 0;\n height: 100%;\n overflow: hidden;\n box-sizing: border-box;\n }\n </style>\n\n <div class="scroll">\n <div class="wrapper">\n <div class="canvases"></div>\n <div class="progress"></div>\n </div>\n </div>\n `,[t,e]}setOptions(t){this.options=t,this.reRender()}getContainer(){return this.scrollContainer}getWrapper(){return this.wrapper}destroy(){this.container.remove(),this.resizeObserver?.disconnect()}delay(t,e=10){return this.timeout&&clearTimeout(this.timeout),new Promise((i=>{this.timeout=setTimeout((()=>{i(t())}),e)}))}async renderPeaks(t,e,i,n){const r=null!=this.options.barWidth?this.options.barWidth*n:1,o=null!=this.options.barGap?this.options.barGap*n:this.options.barWidth?r/2:0,a=this.options.barRadius??0,h=t[0],d=h.length,l=Math.floor(e/(r+o))/d,c=i/2,p=1===t.length,u=p?h:t[1],m=p&&u.some((t=>t<0)),g=(t,i)=>{let s=0,p=0,g=0;const y=document.createElement("canvas");y.width=Math.round(e*(i-t)/d),y.height=this.options.height,y.style.width=`${Math.floor(y.width/n)}px`,y.style.height=`${this.options.height}px`,y.style.left=`${Math.floor(t*e/n/d)}px`,this.canvasWrapper.appendChild(y);const v=y.getContext("2d",{desynchronized:!0});v.beginPath(),v.fillStyle=this.options.waveColor??"",v.roundRect||(v.roundRect=v.fillRect);for(let e=t;e<i;e++){const i=Math.round((e-t)*l);if(i>s){const t=Math.round(p*c),e=Math.round(g*c);v.roundRect(s*(r+o),c-t,r,t+e||1,a),s=i,p=0,g=0}const n=m?h[e]:Math.abs(h[e]),d=m?u[e]:Math.abs(u[e]);n>p&&(p=n),(m?d<-g:d>g)&&(g=d<0?-d:d)}v.fill(),v.closePath();const b=y.cloneNode();this.progressWrapper.appendChild(b);const f=b.getContext("2d",{desynchronized:!0});y.width>0&&y.height>0&&f.drawImage(y,0,0),f.globalCompositeOperation="source-in",f.fillStyle=this.options.progressColor??"",f.fillRect(0,0,y.width,y.height)};this.canvasWrapper.innerHTML="",this.progressWrapper.innerHTML="";const{scrollLeft:y,scrollWidth:v,clientWidth:b}=this.scrollContainer,f=d/v,w=Math.min(s.MAX_CANVAS_WIDTH,v,y+b),C=Math.floor(y*f),x=Math.ceil(w*f);g(C,x);const P=x-C;for(let t=x;t<d;t+=P)await this.delay((()=>{g(t,Math.min(d,t+P))}));for(let t=C-1;t>=0;t-=P)await this.delay((()=>{g(Math.max(0,t-P),t)}))}render(t,e){const i=window.devicePixelRatio||1,s=this.scrollContainer.clientWidth,n=Math.ceil(e*(this.options.minPxPerSec||0));this.isScrolling=n>s;const r=this.options.fillParent&&!this.isScrolling,o=(r?s:n)*i,{height:a}=this.options;this.wrapper.style.width=r?"100%":`${n}px`,this.scrollContainer.style.overflowX=this.isScrolling?"auto":"hidden",this.scrollContainer.classList.toggle("noScrollbar",!!this.options.hideScrollbar),this.progressWrapper.style.borderRightStyle="solid",this.progressWrapper.style.borderRightColor=`${this.options.cursorColor||this.options.progressColor}`,this.progressWrapper.style.borderRightWidth=`${this.options.cursorWidth}px`,this.canvasWrapper.style.height=`${this.options.height}px`,this.renderPeaks(t,o,a,i),this.channelData=t,this.duration=e}reRender(){if(!this.channelData||!this.duration)return;const t=this.progressWrapper.clientWidth;this.render(this.channelData,this.duration);const e=this.progressWrapper.clientWidth;this.scrollContainer.scrollLeft+=e-t}zoom(t){this.options.minPxPerSec=t,this.reRender()}renderProgress(t,e=!1){if(this.progressWrapper.style.width=100*t+"%",this.isScrolling&&this.options.autoCenter){const{clientWidth:i,scrollLeft:s,scrollWidth:n}=this.scrollContainer,r=n*t,o=i/2,a=o/20;(r>s+(e?o:i)||r<s)&&(r-(s+o)>=a&&r<s+i?this.scrollContainer.scrollLeft+=a:this.scrollContainer.scrollLeft=r-o)}}}const n=s,r=class extends i{media;subscriptions=[];isExternalMedia=!1;hasPlayedOnce=!1;constructor(t){super(),t.media?(this.media=t.media,this.isExternalMedia=!0):this.media=document.createElement("audio"),this.subscriptions.push(this.onceMediaEvent("play",(()=>{this.hasPlayedOnce=!0}))),t.autoplay&&(this.media.autoplay=!0),null!=t.playbackRate&&(this.media.playbackRate=t.playbackRate)}onMediaEvent(t,e,i){return this.media.addEventListener(t,e,i),()=>this.media.removeEventListener(t,e)}onceMediaEvent(t,e){return this.onMediaEvent(t,e,{once:!0})}loadUrl(t){this.media.src=t}destroy(){this.media.pause(),this.subscriptions.forEach((t=>t())),this.isExternalMedia||this.media.remove()}play(){return this.media.play()}pause(){this.media.pause()}isPlaying(){return this.media.currentTime>0&&!this.media.paused&&!this.media.ended}setTime(t){this.hasPlayedOnce||this.media.play()?.then?.((()=>{setTimeout((()=>this.media.pause()),10)})),this.media.currentTime=t}getDuration(){return this.media.duration}getCurrentTime(){return this.media.currentTime}getVolume(){return this.media.volume}setVolume(t){this.media.volume=t}getMuted(){return this.media.muted}setMuted(t){this.media.muted=t}getPlaybackRate(){return this.media.playbackRate}setPlaybackRate(t,e){null!=e&&(this.media.preservesPitch=e),this.media.playbackRate=t}getMediaElement(){return this.media}setSinkId(t){return this.media.setSinkId(t)}},o=class extends i{unsubscribe=()=>{};start(){this.unsubscribe=this.on("tick",(()=>{requestAnimationFrame((()=>{this.emit("tick")}))})),this.emit("tick")}stop(){this.unsubscribe()}destroy(){this.unsubscribe()}},a={height:128,waveColor:"#999",progressColor:"#555",cursorWidth:1,minPxPerSec:0,fillParent:!0,interact:!0,autoCenter:!0};class h extends r{options;fetcher;decoder;renderer;timer;plugins=[];decodedData=null;canPlay=!1;static create(t){return new h(t)}constructor(t){super({media:t.media,autoplay:t.autoplay,playbackRate:t.audioRate}),this.options=Object.assign({},a,t),this.fetcher=new class{async load(t){return fetch(t).then((t=>t.arrayBuffer()))}},this.decoder=new class{audioCtx=null;initAudioContext(t){this.audioCtx=new AudioContext({latencyHint:"playback",sampleRate:t})}constructor(){try{this.initAudioContext(3e3)}catch(t){this.initAudioContext(8e3)}}async decode(t){if(!this.audioCtx)throw new Error("AudioContext is not initialized");return this.audioCtx.decodeAudioData(t)}destroy(){this.audioCtx?.close(),this.audioCtx=null}},this.timer=new o,this.renderer=new n({container:this.options.container},this.options),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents(),this.initReadyEvent(),this.initPlugins();const e=this.options.url||this.options.media?.src;e&&this.load(e,this.options.peaks,this.options.duration)}setOptions(t){this.options={...this.options,...t},this.renderer.setOptions(this.options)}initPlayerEvents(){this.subscriptions.push(this.onMediaEvent("timeupdate",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),this.isPlaying()),this.emit("timeupdate",{currentTime:t})})),this.onMediaEvent("play",(()=>{this.emit("play"),this.timer.start()})),this.onMediaEvent("pause",(()=>{this.emit("pause"),this.timer.stop(),this.getCurrentTime()>=this.getDuration()&&this.emit("finish")})),this.onMediaEvent("canplay",(()=>{this.canPlay=!0,this.emit("canplay",{duration:this.getDuration()})})),this.onMediaEvent("seeking",(()=>{this.emit("seeking",{currentTime:this.getCurrentTime()})})))}initRendererEvents(){this.subscriptions.push(this.renderer.on("click",(({relativeX:t})=>{this.options.interact&&(this.seekTo(t),this.emit("interaction"))})))}initTimerEvents(){this.subscriptions.push(this.timer.on("tick",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),!0),this.emit("timeupdate",{currentTime:t})})))}initReadyEvent(){const t=()=>{this.decodedData&&this.canPlay&&this.emit("ready",{duration:this.getDuration()})};this.subscriptions.push(this.on("decode",t),this.on("canplay",t))}initPlugins(){this.options.plugins?.length&&this.options.plugins.forEach((t=>{this.registerPlugin(t)}))}registerPlugin(t){return t.init({wavesurfer:this,container:this.renderer.getContainer(),wrapper:this.renderer.getWrapper()}),this.plugins.push(t),t}async load(t,e,i){if(this.decodedData=null,this.canPlay=!1,this.loadUrl(t),null==e){const e=await this.fetcher.load(t),i=await this.decoder.decode(e);this.decodedData=i}else i||(i=await new Promise((t=>{this.onceMediaEvent("loadedmetadata",(()=>t(this.getDuration())))}))||0),"number"==typeof e[0]&&(e=[e]),this.decodedData={duration:i,numberOfChannels:e.length,sampleRate:e[0].length/i,getChannelData:t=>e?.[t]};this.renderAudio(),this.emit("decode",{duration:this.getDuration()})}renderAudio(){if(!this.decodedData)return;const t=[this.decodedData.getChannelData(0)];this.decodedData.numberOfChannels>1&&t.push(this.decodedData.getChannelData(1));const e=this.getDuration();this.renderer.render(t,e)}zoom(t){if(!this.decodedData)throw new Error("No audio loaded");this.renderer.zoom(t),this.emit("zoom",{minPxPerSec:t})}getDecodedData(){return this.decodedData}getDuration(){const t=this.getMediaElement()?.duration;return t>0&&t<1/0?t:this.decodedData?.duration||0}toggleInteraction(t){this.options.interact=t}seekTo(t){const e=this.getDuration()*t;this.setTime(e)}playPause(){return this.isPlaying()?(this.pause(),Promise.resolve()):this.play()}stop(){this.pause(),this.setTime(0)}skip(t){this.setTime(this.getCurrentTime()+t)}empty(){this.load("",[[0]],.001)}destroy(){this.emit("destroy"),this.plugins.forEach((t=>t.destroy())),this.timer.destroy(),this.decoder.destroy(),this.renderer.destroy(),super.destroy()}}const d=h;return e.default})()));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wavesurfer.js",
|
|
3
|
-
"version": "7.0.0-alpha.
|
|
3
|
+
"version": "7.0.0-alpha.32",
|
|
4
4
|
"license": "BSD-3-Clause",
|
|
5
5
|
"author": "katspaugh",
|
|
6
6
|
"homepage": "https://wavesurfer-js.org",
|
|
@@ -35,13 +35,14 @@
|
|
|
35
35
|
"build:dev": "tsc -w",
|
|
36
36
|
"build:umd": "webpack && webpack --config webpack.config.plugins.js",
|
|
37
37
|
"build": "tsc && npm run build:umd",
|
|
38
|
+
"deploy": "yarn build && yarn docs && rm public/dist && rm public/examples && mv dist public/ && mv examples public/",
|
|
38
39
|
"prepublishOnly": "npm run build",
|
|
39
|
-
"docs": "typedoc src/wavesurfer.ts src/plugins/*",
|
|
40
|
+
"docs": "typedoc src/wavesurfer.ts src/plugins/* --out public/docs",
|
|
40
41
|
"lint": "eslint --ext .ts src --fix",
|
|
41
42
|
"prettier": "prettier -w '**/*.{js,ts,css}' --ignore-path .gitignore",
|
|
42
43
|
"cypress": "cypress open",
|
|
43
44
|
"test": "cypress run",
|
|
44
|
-
"serve": "browser-sync start --server --port 9090 --
|
|
45
|
+
"serve": "cd public && browser-sync start --server --port 9090 --watch '*' --no-ghost-mode --no-ui --cwd",
|
|
45
46
|
"start": "npm run build:dev & npm run serve"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|