wavesurfer.js 7.0.0-alpha.1 → 7.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/decoder.js +1 -2
- package/dist/event-emitter.d.ts +2 -0
- package/dist/event-emitter.js +8 -0
- package/dist/index.js +1 -1
- package/dist/player-webaudio.js +1 -2
- package/dist/renderer.d.ts +3 -2
- package/dist/renderer.js +23 -14
- package/dist/wavesurfer.Regions.min.js +1 -0
- package/dist/wavesurfer.min.js +1 -0
- package/package.json +14 -6
- package/.eslintrc.json +0 -24
- package/.prettierrc +0 -8
- package/examples/audio.ogg +0 -0
- package/examples/bars.js +0 -19
- package/examples/basic.js +0 -8
- package/examples/gradient.js +0 -28
- package/examples/regions.js +0 -63
- package/examples/video.js +0 -19
- package/examples/webaudio.js +0 -14
- package/src/base-plugin.ts +0 -20
- package/src/decoder.ts +0 -41
- package/src/event-emitter.ts +0 -35
- package/src/fetcher.ts +0 -7
- package/src/index.ts +0 -252
- package/src/player-webaudio.ts +0 -34
- package/src/player.ts +0 -50
- package/src/plugins/regions.ts +0 -240
- package/src/renderer.ts +0 -250
- package/src/timer.ts +0 -27
- package/tsconfig.json +0 -105
- package/tutorial/index.html +0 -47
- package/tutorial/src/editor.js +0 -70
- package/tutorial/src/init.js +0 -66
- package/tutorial/src/url.js +0 -25
- package/tutorial/style.css +0 -211
- package/yarn-error.log +0 -1049
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ yarn
|
|
|
33
33
|
Start the TypeScript compiler in watch mode:
|
|
34
34
|
|
|
35
35
|
```
|
|
36
|
-
yarn build
|
|
36
|
+
yarn build:dev
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
Run an HTTP server to view the examples:
|
|
@@ -42,7 +42,7 @@ Run an HTTP server to view the examples:
|
|
|
42
42
|
python3 -m http.server --cgi 8080
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
Open http://localhost:8080/
|
|
45
|
+
Open http://localhost:8080/tutorial in your browser.
|
|
46
46
|
There's no hot reload yet, so you'll need to reload the page manually on every change.
|
|
47
47
|
|
|
48
48
|
## Feedback
|
package/dist/decoder.js
CHANGED
|
@@ -12,8 +12,7 @@ const SAMPLE_RATE = 3000;
|
|
|
12
12
|
class Decoder {
|
|
13
13
|
constructor() {
|
|
14
14
|
this.audioCtx = null;
|
|
15
|
-
this.audioCtx = new (
|
|
16
|
-
window.webkitAudioContext)({
|
|
15
|
+
this.audioCtx = new AudioContext({
|
|
17
16
|
latencyHint: 'playback',
|
|
18
17
|
sampleRate: SAMPLE_RATE,
|
|
19
18
|
});
|
package/dist/event-emitter.d.ts
CHANGED
|
@@ -7,5 +7,7 @@ declare class EventEmitter<EventTypes extends GeneralEventTypes> {
|
|
|
7
7
|
protected emit<T extends keyof EventTypes>(eventType: T, detail?: EventTypes[T]): void;
|
|
8
8
|
/** Subscribe to an event and return a function to unsubscribe */
|
|
9
9
|
on<T extends keyof EventTypes>(eventType: T, callback: (detail: EventTypes[T]) => void): () => void;
|
|
10
|
+
/** Subscribe to an event once and return a function to unsubscribe */
|
|
11
|
+
once<T extends keyof EventTypes>(eventType: T, callback: (detail: EventTypes[T]) => void): () => void;
|
|
10
12
|
}
|
|
11
13
|
export default EventEmitter;
|
package/dist/event-emitter.js
CHANGED
|
@@ -17,5 +17,13 @@ class EventEmitter {
|
|
|
17
17
|
this.eventTarget.addEventListener(eventName, handler);
|
|
18
18
|
return () => this.eventTarget.removeEventListener(eventName, handler);
|
|
19
19
|
}
|
|
20
|
+
/** Subscribe to an event once and return a function to unsubscribe */
|
|
21
|
+
once(eventType, callback) {
|
|
22
|
+
const unsubscribe = this.on(eventType, (...args) => {
|
|
23
|
+
unsubscribe();
|
|
24
|
+
callback(...args);
|
|
25
|
+
});
|
|
26
|
+
return unsubscribe;
|
|
27
|
+
}
|
|
20
28
|
}
|
|
21
29
|
export default EventEmitter;
|
package/dist/index.js
CHANGED
|
@@ -127,7 +127,7 @@ export class WaveSurfer extends EventEmitter {
|
|
|
127
127
|
if (this.channelData == null || this.duration == null) {
|
|
128
128
|
throw new Error('No audio loaded');
|
|
129
129
|
}
|
|
130
|
-
this.renderer.
|
|
130
|
+
this.renderer.zoom(this.channelData, this.duration, minPxPerSec);
|
|
131
131
|
}
|
|
132
132
|
/** Start playing the audio */
|
|
133
133
|
play() {
|
package/dist/player-webaudio.js
CHANGED
|
@@ -16,8 +16,7 @@ class WebAudioPlayer extends Player {
|
|
|
16
16
|
loadUrl(url) {
|
|
17
17
|
super.loadUrl(url);
|
|
18
18
|
if (!this.audioCtx) {
|
|
19
|
-
this.audioCtx = new (
|
|
20
|
-
window.webkitAudioContext)({
|
|
19
|
+
this.audioCtx = new AudioContext({
|
|
21
20
|
latencyHint: 'playback',
|
|
22
21
|
});
|
|
23
22
|
}
|
package/dist/renderer.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ type RendererEvents = {
|
|
|
17
17
|
declare class Renderer extends EventEmitter<RendererEvents> {
|
|
18
18
|
private options;
|
|
19
19
|
private container;
|
|
20
|
-
private
|
|
20
|
+
private scrollContainer;
|
|
21
21
|
private mainCanvas;
|
|
22
22
|
private progressCanvas;
|
|
23
23
|
private cursor;
|
|
@@ -26,8 +26,9 @@ declare class Renderer extends EventEmitter<RendererEvents> {
|
|
|
26
26
|
destroy(): void;
|
|
27
27
|
private renderLinePeaks;
|
|
28
28
|
private renderBarPeaks;
|
|
29
|
-
render(channelData: Float32Array[], duration: number, minPxPerSec?: number): void;
|
|
30
29
|
private createProgressMask;
|
|
30
|
+
render(channelData: Float32Array[], duration: number): void;
|
|
31
|
+
zoom(channelData: Float32Array[], duration: number, minPxPerSec: number): void;
|
|
31
32
|
renderProgress(progress: number, autoCenter?: boolean): void;
|
|
32
33
|
getContainer(): HTMLElement;
|
|
33
34
|
}
|
package/dist/renderer.js
CHANGED
|
@@ -66,7 +66,7 @@ class Renderer extends EventEmitter {
|
|
|
66
66
|
</div>
|
|
67
67
|
`;
|
|
68
68
|
this.container = div;
|
|
69
|
-
this.
|
|
69
|
+
this.scrollContainer = shadow.querySelector('.scroll');
|
|
70
70
|
this.mainCanvas = shadow.querySelector('canvas');
|
|
71
71
|
this.ctx = this.mainCanvas.getContext('2d', { desynchronized: true });
|
|
72
72
|
this.progressCanvas = shadow.querySelector('.progress');
|
|
@@ -150,17 +150,6 @@ class Renderer extends EventEmitter {
|
|
|
150
150
|
ctx.fillStyle = this.options.waveColor;
|
|
151
151
|
ctx.fill();
|
|
152
152
|
}
|
|
153
|
-
render(channelData, duration, minPxPerSec = this.options.minPxPerSec) {
|
|
154
|
-
const { devicePixelRatio } = window;
|
|
155
|
-
const width = Math.max(this.container.clientWidth * devicePixelRatio, duration * minPxPerSec);
|
|
156
|
-
const { height } = this.options;
|
|
157
|
-
this.mainCanvas.width = width;
|
|
158
|
-
this.mainCanvas.height = height;
|
|
159
|
-
this.mainCanvas.style.width = Math.round(width / devicePixelRatio) + 'px';
|
|
160
|
-
const renderingFn = this.options.barWidth ? this.renderBarPeaks : this.renderLinePeaks;
|
|
161
|
-
renderingFn.call(this, channelData, width, height);
|
|
162
|
-
this.createProgressMask();
|
|
163
|
-
}
|
|
164
153
|
createProgressMask() {
|
|
165
154
|
const progressCtx = this.progressCanvas.getContext('2d', { desynchronized: true });
|
|
166
155
|
if (!progressCtx) {
|
|
@@ -175,6 +164,26 @@ class Renderer extends EventEmitter {
|
|
|
175
164
|
progressCtx.fillStyle = this.options.progressColor;
|
|
176
165
|
progressCtx.fillRect(0, 0, this.progressCanvas.width, this.progressCanvas.height);
|
|
177
166
|
}
|
|
167
|
+
render(channelData, duration) {
|
|
168
|
+
const { devicePixelRatio } = window;
|
|
169
|
+
const width = Math.max(this.container.clientWidth * devicePixelRatio, duration * this.options.minPxPerSec);
|
|
170
|
+
const { height } = this.options;
|
|
171
|
+
this.mainCanvas.width = width;
|
|
172
|
+
this.mainCanvas.height = height;
|
|
173
|
+
this.mainCanvas.style.width = Math.round(width / devicePixelRatio) + 'px';
|
|
174
|
+
const renderingFn = this.options.barWidth ? this.renderBarPeaks : this.renderLinePeaks;
|
|
175
|
+
renderingFn.call(this, channelData, width, height);
|
|
176
|
+
this.createProgressMask();
|
|
177
|
+
}
|
|
178
|
+
zoom(channelData, duration, minPxPerSec) {
|
|
179
|
+
// Remember the current cursor position
|
|
180
|
+
const oldCursorPosition = this.cursor.getBoundingClientRect().left;
|
|
181
|
+
this.options.minPxPerSec = minPxPerSec;
|
|
182
|
+
this.render(channelData, duration);
|
|
183
|
+
// Adjust the scroll position so that the cursor stays in the same place
|
|
184
|
+
const newCursortPosition = this.cursor.getBoundingClientRect().left;
|
|
185
|
+
this.scrollContainer.scrollLeft += newCursortPosition - oldCursorPosition;
|
|
186
|
+
}
|
|
178
187
|
renderProgress(progress, autoCenter = false) {
|
|
179
188
|
this.progressCanvas.style.clipPath = `inset(0 ${100 - progress * 100}% 0 0)`;
|
|
180
189
|
this.cursor.style.left = `${progress * 100}%`;
|
|
@@ -182,12 +191,12 @@ class Renderer extends EventEmitter {
|
|
|
182
191
|
const center = this.container.clientWidth / 2;
|
|
183
192
|
const fullWidth = this.mainCanvas.clientWidth;
|
|
184
193
|
if (fullWidth * progress >= center) {
|
|
185
|
-
this.
|
|
194
|
+
this.scrollContainer.scrollLeft = fullWidth * progress - center;
|
|
186
195
|
}
|
|
187
196
|
}
|
|
188
197
|
}
|
|
189
198
|
getContainer() {
|
|
190
|
-
return this.
|
|
199
|
+
return this.scrollContainer;
|
|
191
200
|
}
|
|
192
201
|
}
|
|
193
202
|
export default Renderer;
|
|
@@ -0,0 +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={d:(t,i)=>{for(var n in i)e.o(i,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:i[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>s});const i=class{constructor(){this.eventTarget=new EventTarget}emit(e,t){const i=new CustomEvent(String(e),{detail:t});this.eventTarget.dispatchEvent(i)}on(e,t){const i=e=>{e instanceof CustomEvent&&t(e.detail)},n=String(e);return this.eventTarget.addEventListener(n,i),()=>this.eventTarget.removeEventListener(n,i)}once(e,t){const i=this.on(e,((...e)=>{i(),t(...e)}));return i}},n=class extends i{constructor(e){super(),this.subscriptions=[],this.wavesurfer=e.wavesurfer,this.renderer=e.renderer}destroy(){this.subscriptions.forEach((e=>e()))}},s=class extends n{constructor(e){super(e),this.dragStart=NaN,this.regions=[],this.createdRegion=null,this.modifiedRegion=null,this.isResizingLeft=!1,this.isMoving=!1,this.handleMouseDown=e=>{this.dragStart=e.clientX-this.container.getBoundingClientRect().left},this.handleMouseMove=e=>{const t=e.clientX-this.container.getBoundingClientRect().left;if(this.modifiedRegion&&this.isMoving)return this.moveRegion(this.modifiedRegion,t-this.dragStart),void(this.dragStart=t);if(this.modifiedRegion)this.updateRegion(this.modifiedRegion,this.isResizingLeft?t:void 0,this.isResizingLeft?void 0:t);else if(!isNaN(this.dragStart)){const t=e.clientX-this.container.getBoundingClientRect().left;t-this.dragStart>=10&&(this.createdRegion?this.updateRegion(this.createdRegion,this.dragStart,t):(this.renderer.getContainer().style.pointerEvents="none",this.createdRegion=this.createRegion(this.dragStart,t)))}},this.handleMouseUp=()=>{this.createdRegion&&(this.addRegion(this.createdRegion),this.createdRegion=null),this.modifiedRegion=null,this.isMoving=!1,this.dragStart=NaN,this.renderer.getContainer().style.pointerEvents=""},this.container=this.initContainer();const t=this.wavesurfer.on("ready",(()=>{this.renderer.getContainer().appendChild(this.container),t()}));this.subscriptions.push(t),this.renderer.getContainer().addEventListener("mousedown",this.handleMouseDown),document.addEventListener("mousemove",this.handleMouseMove),document.addEventListener("mouseup",this.handleMouseUp)}destroy(){var e;this.renderer.getContainer().removeEventListener("mousedown",this.handleMouseDown),document.removeEventListener("mousemove",this.handleMouseMove),document.removeEventListener("mouseup",this.handleMouseUp,!0),null===(e=this.container)||void 0===e||e.remove(),super.destroy()}initContainer(){const e=document.createElement("div");return e.style.position="absolute",e.style.top="0",e.style.left="0",e.style.width="100%",e.style.height="100%",e.style.zIndex="3",e.style.pointerEvents="none",e}createRegionElement(e,t,i=""){const n=document.createElement("div");n.style.position="absolute",n.style.left=`${e}px`,n.style.width=t-e+"px",n.style.height="100%",n.style.backgroundColor="rgba(0, 0, 0, 0.1)",n.style.transition="background-color 0.2s ease",n.style.cursor="move",n.style.pointerEvents="all",n.title=i;const s=document.createElement("div");s.style.position="absolute",s.style.left="0",s.style.width="6px",s.style.height="100%",s.style.borderLeft="2px solid rgba(0, 0, 0, 0.5)",s.style.cursor="ew-resize",s.style.pointerEvents="all",n.appendChild(s);const o=document.createElement("div");return o.style.position="absolute",o.style.right="0",o.style.width="6px",o.style.height="100%",o.style.borderRight="2px solid rgba(0, 0, 0, 0.5)",o.style.cursor="ew-resize",o.style.pointerEvents="all",n.appendChild(o),s.addEventListener("mousedown",(e=>{e.stopPropagation(),this.modifiedRegion=this.regions.find((e=>e.element===n))||null,this.isResizingLeft=!0,this.isMoving=!1})),o.addEventListener("mousedown",(e=>{e.stopPropagation(),this.modifiedRegion=this.regions.find((e=>e.element===n))||null,this.isResizingLeft=!1,this.isMoving=!1})),n.addEventListener("mousedown",(()=>{this.modifiedRegion=this.regions.find((e=>e.element===n))||null,this.isMoving=!0})),n.addEventListener("click",(()=>{const e=this.regions.find((e=>e.element===n));e&&this.emit("region-clicked",{region:e})})),this.container.appendChild(n),n}createRegion(e,t,i=""){const n=this.wavesurfer.getDuration(),s=this.container.clientWidth;return{element:this.createRegionElement(e,t,i),start:e,end:t,startTime:e/s*n,endTime:t/s*n,title:i}}addRegion(e){this.regions.push(e),this.emit("region-created",{region:e})}updateRegion(e,t,i){null!=t&&(e.start=null!=t?t:e.start,e.element.style.left=`${e.start}px`,e.startTime=t/this.container.clientWidth*this.wavesurfer.getDuration()),null!=i&&(e.end=i,e.element.style.width=e.end-e.start+"px",e.endTime=i/this.container.clientWidth*this.wavesurfer.getDuration()),this.emit("region-updated",{region:e})}moveRegion(e,t){this.updateRegion(e,e.start+t,e.end+t)}addRegionAtTime(e,t,i=""){const n=this.wavesurfer.getDuration(),s=this.container.clientWidth,o=e/n*s,r=t/n*s,d=this.createRegion(o,r,i);return this.addRegion(d),d}setRegionColor(e,t){e.element.style.backgroundColor=t}};return t.default})()));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.WaveSurfer=e():t.WaveSurfer=e()}(this,(()=>(()=>{"use strict";var t={d:(e,i)=>{for(var n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:i[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>c});const i=class{constructor(){this.eventTarget=new EventTarget}emit(t,e){const i=new CustomEvent(String(t),{detail:e});this.eventTarget.dispatchEvent(i)}on(t,e){const i=t=>{t instanceof CustomEvent&&e(t.detail)},n=String(t);return this.eventTarget.addEventListener(n,i),()=>this.eventTarget.removeEventListener(n,i)}once(t,e){const i=this.on(t,((...t)=>{i(),e(...t)}));return i}},n=class extends i{constructor(t){super(),this.options=t;let e=null;if("string"==typeof this.options.container?e=document.querySelector(this.options.container):this.options.container instanceof HTMLElement&&(e=this.options.container),!e)throw new Error("Container not found");const i=document.createElement("div"),n=i.attachShadow({mode:"open"});n.innerHTML=`\n <style>\n :host .scroll {\n overflow-x: auto;\n overflow-y: visible;\n user-select: none;\n width: 100%;\n height: ${this.options.height}px;\n position: relative;\n }\n :host .wrapper {\n position: relative;\n width: fit-content;\n min-width: 100%;\n height: 100%;\n z-index: 2;\n }\n :host canvas {\n display: block;\n height: 100%;\n min-width: 100%;\n image-rendering: pixelated;\n }\n :host .progress {\n position: absolute;\n z-index: 2;\n top: 0;\n left: 0;\n height: 100%;\n pointer-events: none;\n clip-path: inset(100%);\n }\n :host .cursor {\n position: absolute;\n z-index: 3;\n top: 0;\n left: 0;\n height: 100%;\n border-right: 1px solid ${this.options.progressColor};\n }\n </style>\n\n <div class="scroll">\n <div class="wrapper">\n <canvas></canvas>\n <canvas class="progress"></canvas>\n <div class="cursor"></div>\n </div>\n </div>\n `,this.container=i,this.scrollContainer=n.querySelector(".scroll"),this.mainCanvas=n.querySelector("canvas"),this.ctx=this.mainCanvas.getContext("2d",{desynchronized:!0}),this.progressCanvas=n.querySelector(".progress"),this.cursor=n.querySelector(".cursor"),e.appendChild(i),this.mainCanvas.addEventListener("click",(t=>{const e=this.mainCanvas.getBoundingClientRect(),i=(t.clientX-e.left)/e.width;this.emit("click",{relativeX:i})}))}destroy(){this.container.remove()}renderLinePeaks(t,e,i){const{ctx:n}=this;n.clearRect(0,0,e,i),n.beginPath(),n.moveTo(0,i/2);const s=t[0],o=1===t.length,r=o?s:t[1];let a=-1,h=0;for(let t=0,o=s.length;t<o;t++){const o=Math.round(t/s.length*e),r=Math.round((1-s[t])*i/2);(o!==a||r>h)&&(n.lineTo(o,r),a=o,h=r)}a=-1,h=0;for(let t=r.length-1;t>=0;t--){const s=Math.round(t/r.length*e),l=Math.round((1+r[t])*i/2);(s!==a||(o?l<-h:l>h))&&(n.lineTo(s,l),a=s,h=l)}n.strokeStyle=n.fillStyle=this.options.waveColor,n.stroke(),n.fill()}renderBarPeaks(t,e,i){var n,s;const{devicePixelRatio:o}=window,{ctx:r}=this;r.clearRect(0,0,e,i);const a=this.options.barWidth||1,h=a*o,l=(null!==(n=this.options.barGap)&&void 0!==n?n:a/2)*o,c=null!==(s=this.options.barRadius)&&void 0!==s?s:0,d=t[0],u=1===t.length,p=u?d:t[1],v=Math.floor(e/(h+l)),m=new Float32Array(v),g=new Float32Array(v),y=v/d.length,f=d.length;for(let t=0;t<f;t++){const e=Math.round(t*y);m[e]=Math.max(m[e],d[t]),g[e]=(u?Math.min:Math.max)(g[e],p[t])}r.beginPath();for(let t=0;t<v;t++){const e=Math.max(1,Math.round(m[t]*i/2)),n=Math.max(1,Math.round(Math.abs(g[t]*i)/2));r.roundRect(t*(h+l),i/2-e,h,e+n,c)}r.fillStyle=this.options.waveColor,r.fill()}createProgressMask(){const t=this.progressCanvas.getContext("2d",{desynchronized:!0});if(!t)throw new Error("Failed to get canvas context");this.progressCanvas.width=this.mainCanvas.width,this.progressCanvas.height=this.mainCanvas.height,this.progressCanvas.style.width=this.mainCanvas.style.width,this.progressCanvas.style.height=this.mainCanvas.style.height,t.drawImage(this.mainCanvas,0,0),t.globalCompositeOperation="source-in",t.fillStyle=this.options.progressColor,t.fillRect(0,0,this.progressCanvas.width,this.progressCanvas.height)}render(t,e){const{devicePixelRatio:i}=window,n=Math.max(this.container.clientWidth*i,e*this.options.minPxPerSec),{height:s}=this.options;this.mainCanvas.width=n,this.mainCanvas.height=s,this.mainCanvas.style.width=Math.round(n/i)+"px",(this.options.barWidth?this.renderBarPeaks:this.renderLinePeaks).call(this,t,n,s),this.createProgressMask()}zoom(t,e,i){const n=this.cursor.getBoundingClientRect().left;this.options.minPxPerSec=i,this.render(t,e);const s=this.cursor.getBoundingClientRect().left;this.scrollContainer.scrollLeft+=s-n}renderProgress(t,e=!1){if(this.progressCanvas.style.clipPath=`inset(0 ${100-100*t}% 0 0)`,this.cursor.style.left=100*t+"%",e){const e=this.container.clientWidth/2,i=this.mainCanvas.clientWidth;i*t>=e&&(this.scrollContainer.scrollLeft=i*t-e)}}getContainer(){return this.scrollContainer}},s=class{constructor({media:t}){this.isExternalMedia=!1,t?(this.media=t,this.isExternalMedia=!0):this.media=document.createElement("audio")}on(t,e){return this.media.addEventListener(t,e),()=>this.media.removeEventListener(t,e)}destroy(){this.isExternalMedia||this.media.remove()}loadUrl(t){this.media.src=t}getCurrentTime(){return this.media.currentTime}play(){this.media.play()}pause(){this.media.pause()}isPlaying(){return this.media.currentTime>0&&!this.media.paused&&!this.media.ended}seekTo(t){this.media.currentTime=t}},o=class extends s{constructor(){super(...arguments),this.audioCtx=null,this.sourceNode=null}destroy(){var t,e;null===(t=this.sourceNode)||void 0===t||t.disconnect(),this.sourceNode=null,null===(e=this.audioCtx)||void 0===e||e.close(),this.audioCtx=null,super.destroy()}loadUrl(t){super.loadUrl(t),this.audioCtx||(this.audioCtx=new AudioContext({latencyHint:"playback"})),this.sourceNode&&this.sourceNode.disconnect(),this.sourceNode=this.audioCtx.createMediaElementSource(this.media),this.sourceNode.connect(this.audioCtx.destination)}},r=class extends i{constructor(){super(),this.unsubscribe=()=>{},this.unsubscribe=this.on("tick",(()=>{requestAnimationFrame((()=>{this.emit("tick")}))})),this.emit("tick")}destroy(){this.unsubscribe()}};var a;!function(t){t.WebAudio="WebAudio",t.MediaElement="MediaElement"}(a||(a={}));const h={height:128,waveColor:"#999",progressColor:"#555",minPxPerSec:0,backend:"MediaElement"};class l extends i{static create(t){return new l(t)}constructor(t){var e;super(),this.plugins=[],this.subscriptions=[],this.channelData=null,this.duration=0,this.options=Object.assign({},h,t),this.fetcher=new class{load(t){return e=this,i=void 0,s=function*(){return fetch(t).then((t=>t.arrayBuffer()))},new((n=void 0)||(n=Promise))((function(t,o){function r(t){try{h(s.next(t))}catch(t){o(t)}}function a(t){try{h(s.throw(t))}catch(t){o(t)}}function h(e){var i;e.done?t(e.value):(i=e.value,i instanceof n?i:new n((function(t){t(i)}))).then(r,a)}h((s=s.apply(e,i||[])).next())}));var e,i,n,s}},this.decoder=new class{constructor(){this.audioCtx=null,this.audioCtx=new AudioContext({latencyHint:"playback",sampleRate:3e3})}decode(t){return e=this,i=void 0,s=function*(){if(!this.audioCtx)throw new Error("AudioContext is not initialized");const e=yield this.audioCtx.decodeAudioData(t),i=[e.getChannelData(0)];return e.numberOfChannels>1&&i.push(e.getChannelData(1)),{duration:e.duration,channelData:i}},new((n=void 0)||(n=Promise))((function(t,o){function r(t){try{h(s.next(t))}catch(t){o(t)}}function a(t){try{h(s.throw(t))}catch(t){o(t)}}function h(e){var i;e.done?t(e.value):(i=e.value,i instanceof n?i:new n((function(t){t(i)}))).then(r,a)}h((s=s.apply(e,i||[])).next())}));var e,i,n,s}destroy(){var t;null===(t=this.audioCtx)||void 0===t||t.close(),this.audioCtx=null}},this.timer=new r,this.player=new(this.options.backend===a.WebAudio?o:s)({media:this.options.media}),this.renderer=new n({container:this.options.container,height:this.options.height,waveColor:this.options.waveColor,progressColor:this.options.progressColor,minPxPerSec:this.options.minPxPerSec,barWidth:this.options.barWidth,barGap:this.options.barGap,barRadius:this.options.barRadius}),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents();const i=this.options.url||(null===(e=this.options.media)||void 0===e?void 0:e.src);i&&this.load(i,this.options.channelData,this.options.duration)}initPlayerEvents(){this.subscriptions.push(this.player.on("timeupdate",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.duration,this.isPlaying()),this.emit("audioprocess",{currentTime:t})})),this.player.on("play",(()=>{this.emit("play")})),this.player.on("pause",(()=>{this.emit("pause")})),this.player.on("canplay",(()=>{this.emit("canplay")})),this.player.on("seeking",(()=>{this.emit("seek",{time:this.getCurrentTime()})})))}initRendererEvents(){this.subscriptions.push(this.renderer.on("click",(({relativeX:t})=>{const e=this.getDuration()*t;this.seekTo(e)})))}initTimerEvents(){this.subscriptions.push(this.timer.on("tick",(()=>{if(this.isPlaying()){const t=this.getCurrentTime();this.renderer.renderProgress(t/this.duration,!0),this.emit("audioprocess",{currentTime:t})}})))}destroy(){this.subscriptions.forEach((t=>t())),this.plugins.forEach((t=>t.destroy())),this.timer.destroy(),this.player.destroy(),this.decoder.destroy(),this.renderer.destroy()}load(t,e,i){return n=this,s=void 0,r=function*(){if(this.player.loadUrl(t),null==e||null==i){const n=yield this.fetcher.load(t),s=yield this.decoder.decode(n);e=s.channelData,i=s.duration}this.channelData=e,this.duration=i,this.renderer.render(this.channelData,this.duration),this.emit("ready",{duration:this.duration})},new((o=void 0)||(o=Promise))((function(t,e){function i(t){try{h(r.next(t))}catch(t){e(t)}}function a(t){try{h(r.throw(t))}catch(t){e(t)}}function h(e){var n;e.done?t(e.value):(n=e.value,n instanceof o?n:new o((function(t){t(n)}))).then(i,a)}h((r=r.apply(n,s||[])).next())}));var n,s,o,r}zoom(t){if(null==this.channelData||null==this.duration)throw new Error("No audio loaded");this.renderer.zoom(this.channelData,this.duration,t)}play(){this.player.play()}pause(){this.player.pause()}seekTo(t){this.player.seekTo(t)}isPlaying(){return this.player.isPlaying()}getDuration(){return this.duration}getCurrentTime(){return this.player.getCurrentTime()}registerPlugin(t){const e=new t({wavesurfer:this,renderer:this.renderer});return this.plugins.push(e),e}}const c=l;return e.default})()));
|
package/package.json
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wavesurfer.js",
|
|
3
|
-
"version": "7.0.0-alpha.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "7.0.0-alpha.3",
|
|
4
|
+
"description": "wavesurfer.js is an audio library that draws waveforms and plays audio in the browser.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"files": ["dist/"],
|
|
7
8
|
"scripts": {
|
|
8
|
-
"build": "tsc",
|
|
9
|
+
"build:dev": "tsc -w",
|
|
10
|
+
"build:umd": "webpack && webpack --config webpack.config.plugins.js",
|
|
11
|
+
"prepublishOnly": "npm run build:umd",
|
|
9
12
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
10
13
|
"lint": "eslint --ext .ts src --fix",
|
|
11
14
|
"prettier": "prettier -w src",
|
|
12
|
-
"docs": "npx typedoc src/index.ts"
|
|
15
|
+
"docs": "npx typedoc src/index.ts",
|
|
16
|
+
"serve": "python3 -m http.server --cgi 8080"
|
|
13
17
|
},
|
|
14
18
|
"author": "katspaugh <katspaugh@gmail.com>",
|
|
15
19
|
"license": "ISC",
|
|
@@ -20,7 +24,11 @@
|
|
|
20
24
|
"eslint-config-prettier": "^8.7.0",
|
|
21
25
|
"eslint-plugin-prettier": "^4.2.1",
|
|
22
26
|
"prettier": "^2.8.4",
|
|
27
|
+
"ts-loader": "^9.4.2",
|
|
23
28
|
"typedoc": "^0.23.26",
|
|
24
|
-
"typescript": "^4.9.5"
|
|
25
|
-
|
|
29
|
+
"typescript": "^4.9.5",
|
|
30
|
+
"webpack": "^5.76.1",
|
|
31
|
+
"webpack-cli": "^5.0.1"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {}
|
|
26
34
|
}
|
package/.eslintrc.json
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"env": {
|
|
3
|
-
"browser": true,
|
|
4
|
-
"es2021": true
|
|
5
|
-
},
|
|
6
|
-
"extends": [
|
|
7
|
-
"eslint:recommended",
|
|
8
|
-
"plugin:@typescript-eslint/recommended",
|
|
9
|
-
"prettier",
|
|
10
|
-
"plugin:prettier/recommended"
|
|
11
|
-
],
|
|
12
|
-
"overrides": [
|
|
13
|
-
],
|
|
14
|
-
"parser": "@typescript-eslint/parser",
|
|
15
|
-
"parserOptions": {
|
|
16
|
-
"ecmaVersion": "latest",
|
|
17
|
-
"sourceType": "module"
|
|
18
|
-
},
|
|
19
|
-
"plugins": [
|
|
20
|
-
"@typescript-eslint"
|
|
21
|
-
],
|
|
22
|
-
"rules": {
|
|
23
|
-
}
|
|
24
|
-
}
|
package/.prettierrc
DELETED
package/examples/audio.ogg
DELETED
|
Binary file
|
package/examples/bars.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import WaveSurfer from '/dist/index.js'
|
|
2
|
-
|
|
3
|
-
const wavesurfer = WaveSurfer.create({
|
|
4
|
-
container: document.body,
|
|
5
|
-
waveColor: 'rgb(200, 0, 200)',
|
|
6
|
-
progressColor: 'rgb(100, 0, 100)',
|
|
7
|
-
url: 'https://wavesurfer-js.org/example/media/demo.wav',
|
|
8
|
-
|
|
9
|
-
// Set a bar width
|
|
10
|
-
barWidth: 2,
|
|
11
|
-
// Optionally, specify the spacing between bars
|
|
12
|
-
barGap: 1,
|
|
13
|
-
// And the bar radius
|
|
14
|
-
barRadius: 2,
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
wavesurfer.on('seek', function () {
|
|
18
|
-
wavesurfer.play()
|
|
19
|
-
})
|
package/examples/basic.js
DELETED
package/examples/gradient.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import WaveSurfer from '/dist/index.js'
|
|
2
|
-
|
|
3
|
-
// Create a canvas gradient
|
|
4
|
-
const ctx = document.createElement('canvas').getContext('2d')
|
|
5
|
-
const gradient = ctx.createLinearGradient(0, 0, 0, 150)
|
|
6
|
-
gradient.addColorStop(0, 'rgb(200, 0, 200)')
|
|
7
|
-
gradient.addColorStop(0.7, 'rgb(100, 0, 100)')
|
|
8
|
-
gradient.addColorStop(1, 'rgb(0, 0, 0)')
|
|
9
|
-
|
|
10
|
-
// Default style with a gradient
|
|
11
|
-
WaveSurfer.create({
|
|
12
|
-
container: document.body,
|
|
13
|
-
waveColor: gradient,
|
|
14
|
-
height: 200,
|
|
15
|
-
progressColor: 'rgba(0, 0, 100, 0.5)',
|
|
16
|
-
url: 'https://wavesurfer-js.org/example/media/demo.wav'
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// SoundCloud-style bars
|
|
21
|
-
WaveSurfer.create({
|
|
22
|
-
container: document.body,
|
|
23
|
-
waveColor: gradient,
|
|
24
|
-
height: 200,
|
|
25
|
-
barWidth: 2,
|
|
26
|
-
progressColor: 'rgba(0, 0, 100, 0.5)',
|
|
27
|
-
url: 'https://wavesurfer-js.org/example/media/demo.wav'
|
|
28
|
-
})
|
package/examples/regions.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import WaveSurfer from '/dist/index.js'
|
|
2
|
-
import RegionsPlugin from '/dist/plugins/regions.js'
|
|
3
|
-
|
|
4
|
-
// Create an instance of WaveSurfer
|
|
5
|
-
const ws = WaveSurfer.create({
|
|
6
|
-
container: document.body,
|
|
7
|
-
waveColor: 'rgb(200, 0, 200)',
|
|
8
|
-
progressColor: 'rgb(100, 0, 100)',
|
|
9
|
-
url: 'https://wavesurfer-js.org/example/media/demo.wav'
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
// Initialize the Regions plugin
|
|
13
|
-
const wsRegions = ws.registerPlugin(RegionsPlugin)
|
|
14
|
-
|
|
15
|
-
// Give regions a random color when they are created
|
|
16
|
-
const random = (min, max) => Math.random() * (max - min) + min
|
|
17
|
-
wsRegions.on('region-created', ({ region }) => {
|
|
18
|
-
wsRegions.setRegionColor(region, `rgba(${random(0, 255)}, ${random(0, 255)}, ${random(0, 255)}, 0.5)`)
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
// Create some regions at specific time ranges
|
|
22
|
-
ws.on('ready', () => {
|
|
23
|
-
wsRegions.addRegionAtTime(4, 7, 'First region')
|
|
24
|
-
wsRegions.addRegionAtTime(9, 10, 'Middle region')
|
|
25
|
-
wsRegions.addRegionAtTime(12, 17, 'Last region')
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
// Loop a region on click
|
|
29
|
-
let loop = true
|
|
30
|
-
let activeRegion = null
|
|
31
|
-
|
|
32
|
-
wsRegions.on('region-clicked', ({ region }) => {
|
|
33
|
-
ws.seekTo(region.startTime)
|
|
34
|
-
ws.play()
|
|
35
|
-
activeRegion = region
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
// Track the time
|
|
39
|
-
ws.on('audioprocess', ({ currentTime }) => {
|
|
40
|
-
// When the end of the region is reached
|
|
41
|
-
if (activeRegion && ws.isPlaying() && (currentTime >= activeRegion.endTime)) {
|
|
42
|
-
if (loop) {
|
|
43
|
-
// If looping, jump to the start of the region
|
|
44
|
-
ws.seekTo(activeRegion.startTime)
|
|
45
|
-
} else {
|
|
46
|
-
// Otherwise, stop playing
|
|
47
|
-
ws.pause()
|
|
48
|
-
ws.seekTo(activeRegion.endTime)
|
|
49
|
-
activeRegion = null
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
// Toggle looping with a checkbox
|
|
55
|
-
const p = document.createElement('p')
|
|
56
|
-
p.innerHTML = `
|
|
57
|
-
<label>
|
|
58
|
-
<input type="checkbox" checked="${loop}" style="vertical-align: text-top" />
|
|
59
|
-
Loop regions on click
|
|
60
|
-
</label>
|
|
61
|
-
`
|
|
62
|
-
document.body.appendChild(p)
|
|
63
|
-
document.querySelector('input').onclick = (e) => { loop = e.target.checked }
|
package/examples/video.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import WaveSurfer from '/dist/index.js'
|
|
2
|
-
|
|
3
|
-
// Create a video element and add it to the DOM.
|
|
4
|
-
// The video can also be defined in HTML, in which case we just get it by a selector.
|
|
5
|
-
const video = document.createElement('video')
|
|
6
|
-
video.crossOrigin = 'anonymous'
|
|
7
|
-
video.controls = true
|
|
8
|
-
video.style.width = '100%'
|
|
9
|
-
video.src = 'https://wavesurfer-js.org/example/media/nasa.mp4'
|
|
10
|
-
document.body.appendChild(video)
|
|
11
|
-
|
|
12
|
-
// Initialize wavesurfer.js
|
|
13
|
-
const ws = WaveSurfer.create({
|
|
14
|
-
container: document.body,
|
|
15
|
-
waveColor: 'rgb(200, 0, 200)',
|
|
16
|
-
progressColor: 'rgb(100, 0, 100)',
|
|
17
|
-
// Pass the video element in the `media` param
|
|
18
|
-
media: document.querySelector('video'),
|
|
19
|
-
})
|
package/examples/webaudio.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import WaveSurfer from '/dist/index.js'
|
|
2
|
-
|
|
3
|
-
const audio = new Audio()
|
|
4
|
-
audio.controls = true
|
|
5
|
-
audio.src = '/examples/audio.ogg'
|
|
6
|
-
document.body.appendChild(audio)
|
|
7
|
-
|
|
8
|
-
const wavesurfer = WaveSurfer.create({
|
|
9
|
-
container: document.body,
|
|
10
|
-
waveColor: 'rgb(200, 0, 200)',
|
|
11
|
-
progressColor: 'rgb(100, 0, 100)',
|
|
12
|
-
backend: 'WebAudio',
|
|
13
|
-
media: audio,
|
|
14
|
-
})
|
package/src/base-plugin.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import EventEmitter, { type GeneralEventTypes } from './event-emitter.js'
|
|
2
|
-
import type { WaveSurfer, WaveSurferPluginParams } from './index.js'
|
|
3
|
-
|
|
4
|
-
export class BasePlugin<EventTypes extends GeneralEventTypes> extends EventEmitter<EventTypes> {
|
|
5
|
-
protected wavesurfer: WaveSurfer
|
|
6
|
-
protected renderer: WaveSurfer['renderer']
|
|
7
|
-
protected subscriptions: (() => void)[] = []
|
|
8
|
-
|
|
9
|
-
constructor(params: WaveSurferPluginParams) {
|
|
10
|
-
super()
|
|
11
|
-
this.wavesurfer = params.wavesurfer
|
|
12
|
-
this.renderer = params.renderer
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
destroy() {
|
|
16
|
-
this.subscriptions.forEach((unsubscribe) => unsubscribe())
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export default BasePlugin
|
package/src/decoder.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// Web Audio decodeAudioData with a minimum allowed sample rate
|
|
2
|
-
const SAMPLE_RATE = 3000
|
|
3
|
-
|
|
4
|
-
class Decoder {
|
|
5
|
-
audioCtx: AudioContext | null = null
|
|
6
|
-
|
|
7
|
-
constructor() {
|
|
8
|
-
this.audioCtx = new (window.AudioContext ||
|
|
9
|
-
(window as unknown as { webkitAudioContext: AudioContext }).webkitAudioContext)({
|
|
10
|
-
latencyHint: 'playback',
|
|
11
|
-
sampleRate: SAMPLE_RATE,
|
|
12
|
-
})
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
public async decode(audioData: ArrayBuffer): Promise<{
|
|
16
|
-
duration: number
|
|
17
|
-
channelData: Float32Array[]
|
|
18
|
-
}> {
|
|
19
|
-
if (!this.audioCtx) {
|
|
20
|
-
throw new Error('AudioContext is not initialized')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const buffer = await this.audioCtx.decodeAudioData(audioData)
|
|
24
|
-
const channelData = [buffer.getChannelData(0)]
|
|
25
|
-
if (buffer.numberOfChannels > 1) {
|
|
26
|
-
channelData.push(buffer.getChannelData(1))
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
duration: buffer.duration,
|
|
31
|
-
channelData,
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
destroy() {
|
|
36
|
-
this.audioCtx?.close()
|
|
37
|
-
this.audioCtx = null
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export default Decoder
|
package/src/event-emitter.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export interface GeneralEventTypes {
|
|
2
|
-
// the name of the event and the data it dispatches with
|
|
3
|
-
// e.g. 'entryCreated': { count: 1 }
|
|
4
|
-
[eventType: string]: unknown
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
class EventEmitter<EventTypes extends GeneralEventTypes> {
|
|
8
|
-
private eventTarget: EventTarget
|
|
9
|
-
|
|
10
|
-
constructor() {
|
|
11
|
-
this.eventTarget = new EventTarget()
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
protected emit<T extends keyof EventTypes>(eventType: T, detail?: EventTypes[T]): void {
|
|
15
|
-
const e = new CustomEvent(String(eventType), { detail })
|
|
16
|
-
this.eventTarget.dispatchEvent(e)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Subscribe to an event and return a function to unsubscribe */
|
|
20
|
-
on<T extends keyof EventTypes>(eventType: T, callback: (detail: EventTypes[T]) => void): () => void {
|
|
21
|
-
const handler = (e: Event) => {
|
|
22
|
-
if (e instanceof CustomEvent) {
|
|
23
|
-
callback(e.detail)
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const eventName = String(eventType)
|
|
28
|
-
|
|
29
|
-
this.eventTarget.addEventListener(eventName, handler)
|
|
30
|
-
|
|
31
|
-
return () => this.eventTarget.removeEventListener(eventName, handler)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export default EventEmitter
|