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.
@@ -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,i)=>{i.d(t,{Z:()=>o});var n=i(139);class s extends n.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 o=s},139:(e,t,i)=>{i.d(t,{Z:()=>n});const n=class{eventTarget;constructor(){this.eventTarget=new EventTarget}emit(e,t){const i=new CustomEvent(String(e),{detail:t});this.eventTarget.dispatchEvent(i)}on(e,t,i){const n=e=>{e instanceof CustomEvent&&t(e.detail)},s=String(e);return this.eventTarget.addEventListener(s,n,{once:i}),()=>this.eventTarget.removeEventListener(s,n)}once(e,t){return this.on(e,t,!0)}}}},t={};function i(n){var s=t[n];if(void 0!==s)return s.exports;var o=t[n]={exports:{}};return e[n](o,o.exports,i),o.exports}i.d=(e,t)=>{for(var n in t)i.o(t,n)&&!i.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var n={};return(()=>{i.d(n,{default:()=>a});var e=i(284);const t={dragSelection:!0,draggable:!0,resizable:!0},s=(e,t)=>{for(const i in t)e.style[i]=t[i]||""},o=(e,t)=>{const i=document.createElement(e);return s(i,t),i};class r extends e.Z{dragStart=NaN;regionsContainer;regions=[];createdRegion=null;modifiedRegion=null;isResizingLeft=!1;isMoving=!1;wasInteractive=!0;constructor(e){super(e||{}),this.options=Object.assign({},t,e),this.regionsContainer=this.initRegionsContainer()}static create(e){return new r(e)}init(e){if(super.init(e),!this.wavesurfer||!this.wrapper)throw Error("WaveSurfer is not initialized");this.wrapper?.appendChild(this.regionsContainer),this.wrapper.addEventListener("mousedown",this.handleMouseDown),document.addEventListener("mousemove",this.handleMouseMove),document.addEventListener("mouseup",this.handleMouseUp)}setOptions(e){this.options={...this.options,...e}}destroy(){this.wrapper?.removeEventListener("mousedown",this.handleMouseDown),document.removeEventListener("mousemove",this.handleMouseMove),document.removeEventListener("mouseup",this.handleMouseUp,!0),this.regionsContainer.remove(),super.destroy()}initRegionsContainer(){return o("div",{position:"absolute",top:"0",left:"0",width:"100%",height:"100%",zIndex:"3",pointerEvents:"none"})}handleMouseDown=e=>{if(this.options.draggable||this.options.resizable||this.options.dragSelection){if(!this.wrapper)return;this.dragStart=e.clientX-this.wrapper.getBoundingClientRect().left}};handleMouseMove=e=>{if(!this.wrapper)return;const t=this.wrapper.getBoundingClientRect(),{width:i}=t,n=e.clientX-t.left;if(this.options.draggable&&this.modifiedRegion&&this.isMoving)return this.moveRegion(this.modifiedRegion,(n-this.dragStart)/i),void(this.dragStart=n);if(this.options.resizable&&this.modifiedRegion)this.updateRegion(this.modifiedRegion,this.isResizingLeft?n/i:void 0,this.isResizingLeft?void 0:n/i);else if(this.options.dragSelection&&!isNaN(this.dragStart)){const t=e.clientX-this.regionsContainer.getBoundingClientRect().left;t-this.dragStart>=10&&(this.createdRegion?this.updateRegion(this.createdRegion,this.dragStart/i,t/i):(this.wasInteractive=this.wavesurfer?.options.interact||!0,this.wavesurfer?.toggleInteraction(!1),this.createdRegion=this.createRegion(this.dragStart/i,t/i)))}};handleMouseUp=()=>{this.createdRegion&&(this.addRegion(this.createdRegion),this.createdRegion=null),this.modifiedRegion=null,this.isMoving=!1,this.dragStart=NaN,this.wavesurfer?.toggleInteraction(this.wasInteractive)};createRegionElement(e,t,i=""){const n=e===t,r=o("div",{position:"absolute",left:100*e+"%",width:100*(t-e)+"%",height:"100%",backgroundColor:n?"":"rgba(0, 0, 0, 0.1)",borderRadius:"2px",boxSizing:"border-box",borderLeft:n?"2px solid rgba(0, 0, 0, 0.5)":"",transition:"background-color 0.2s ease",cursor:this.options.draggable?"move":"",pointerEvents:"all",whiteSpace:n?"nowrap":"",padding:"0.2em"});r.textContent=i;const a=o("div",{position:"absolute",left:"0",top:"0",width:"6px",height:"100%",cursor:this.options.resizable?"ew-resize":"",pointerEvents:"all",borderLeft:n?"":"2px solid rgba(0, 0, 0, 0.5)",borderRadius:"2px 0 0 2px"});r.appendChild(a);const d=a.cloneNode();s(d,{left:"",right:"0",borderLeft:"",borderRight:n?"":"2px solid rgba(0, 0, 0, 0.5)",borderRadius:"0 2px 2px 0"}),r.appendChild(d),a.addEventListener("mousedown",(e=>{this.options.resizable&&(e.stopPropagation(),this.modifiedRegion=this.regions.find((e=>e.element===r))||null,this.isResizingLeft=!0,this.isMoving=!1)})),d.addEventListener("mousedown",(e=>{this.options.resizable&&(e.stopPropagation(),this.modifiedRegion=this.regions.find((e=>e.element===r))||null,this.isResizingLeft=!1,this.isMoving=!1)})),r.addEventListener("mousedown",(()=>{this.options.draggable&&(this.modifiedRegion=this.regions.find((e=>e.element===r))||null,this.isMoving=null!==this.modifiedRegion)})),r.addEventListener("click",(()=>{const e=this.regions.find((e=>e.element===r));e&&this.emit("region-clicked",{region:e})})),this.regionsContainer.appendChild(r);const h=r.getBoundingClientRect().left,l=r.scrollWidth,g=this.regions.filter((e=>{const{left:t}=e.element.getBoundingClientRect(),i=e.element.scrollWidth;return h<t+i&&t<h+l})).map((e=>parseFloat(e.element.style.paddingTop))).reduce(((e,t)=>e+t),0);return g>0&&(r.style.paddingTop=`${g+1}em`),r}createRegion(e,t,i=""){const n=this.wavesurfer?.getDuration()||0;return{element:this.createRegionElement(e,t,i),start:e,end:t,startTime:e*n,endTime:t*n,title:i}}addRegion(e){this.regions.push(e),this.emit("region-created",{region:e})}updateRegion(e,t,i){const n=this.wavesurfer?.getDuration()||0;null!=t&&(e.start=t,e.element.style.left=100*e.start+"%",e.element.style.width=100*(e.end-e.start)+"%",e.startTime=t*n),null!=i&&(e.end=i,e.element.style.width=100*(e.end-e.start)+"%",e.endTime=i*n),this.emit("region-updated",{region:e})}moveRegion(e,t){this.updateRegion(e,e.start+t,e.end+t)}add(e,t,i="",n=""){const s=this.wavesurfer?.getDuration()||0,o=e/s,r=t/s,a=this.createRegion(o,r,i);return this.addRegion(a),n&&this.setRegionColor(a,n),a}remove(e){e.element.remove(),e.element=null,this.regions=this.regions.filter((t=>t!==e))}setRegionColor(e,t){e.element.style[e.startTime===e.endTime?"borderColor":"backgroundColor"]=t}setRegionInteractive(e,t){e.element.style.pointerEvents=t?"all":"none"}}const a=r})(),n.default})()));
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;
@@ -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 audioData;
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(audioData: AudioBuffer): void;
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
- audioData = null;
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
- min-width: 100%;
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 if it has alredy been rendered
109
- if (this.audioData)
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(Math.min(scrollWidth, scrollLeft + clientWidth) * scale);
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(audioData) {
228
+ render(channelData, duration) {
225
229
  // Determine the width of the waveform
226
230
  const pixelRatio = window.devicePixelRatio || 1;
227
- const parentWidth = this.options.fillParent ? this.scrollContainer.clientWidth * pixelRatio : 0;
228
- const scrollWidth = this.options.minPxPerSec ? Math.max(1, audioData.duration * this.options.minPxPerSec) : 0;
229
- const width = scrollWidth > parentWidth ? scrollWidth : parentWidth;
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
- this.isScrolling = width > parentWidth;
232
- // Set the width of the container
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.audioData = audioData;
250
+ this.channelData = channelData;
251
+ this.duration = duration;
249
252
  }
250
253
  reRender() {
251
- if (!this.audioData)
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.audioData);
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;
@@ -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 */
@@ -145,8 +145,19 @@ class WaveSurfer extends Player {
145
145
  getChannelData: (i) => channelData?.[i],
146
146
  };
147
147
  }
148
- this.renderer.render(this.decodedData);
149
- this.emit('decode', { duration: this.decodedData.duration });
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) {
@@ -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.30",
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 --startPath /tutorial --watch '*'",
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": {