wavesurfer.js 7.0.0-alpha.3 → 7.0.0-alpha.30
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 +58 -26
- package/dist/base-plugin.d.ts +9 -5
- package/dist/base-plugin.js +11 -3
- package/dist/decoder.d.ts +2 -4
- package/dist/decoder.js +20 -31
- package/dist/event-emitter.d.ts +1 -1
- package/dist/event-emitter.js +4 -7
- package/dist/fetcher.js +2 -13
- package/dist/legacy-adapter.d.ts +7 -0
- package/dist/legacy-adapter.js +15 -0
- package/dist/player.d.ts +39 -9
- package/dist/player.js +91 -15
- package/dist/plugin/wavesurfer.envelope.min.js +1 -0
- package/dist/plugin/wavesurfer.minimap.min.js +1 -0
- package/dist/plugin/wavesurfer.multitrack.min.js +1 -0
- package/dist/plugin/wavesurfer.regions.min.js +1 -0
- package/dist/plugin/wavesurfer.timeline.min.js +1 -0
- package/dist/plugins/envelope.d.ts +60 -0
- package/dist/plugins/envelope.js +302 -0
- package/dist/plugins/envelope.min.js +1 -0
- package/dist/plugins/minimap.d.ts +27 -0
- package/dist/plugins/minimap.js +83 -0
- package/dist/plugins/minimap.min.js +1 -0
- package/dist/plugins/multitrack.d.ts +113 -0
- package/dist/plugins/multitrack.js +494 -0
- package/dist/plugins/multitrack.min.js +1 -0
- package/dist/plugins/record.d.ts +23 -0
- package/dist/plugins/record.js +119 -0
- package/dist/plugins/record.min.js +1 -0
- package/dist/plugins/regions.d.ts +23 -9
- package/dist/plugins/regions.js +196 -120
- package/dist/plugins/regions.min.js +1 -0
- package/dist/plugins/timeline.d.ts +38 -0
- package/dist/plugins/timeline.js +134 -0
- package/dist/plugins/timeline.min.js +1 -0
- package/dist/renderer.d.ts +25 -12
- package/dist/renderer.js +222 -138
- package/dist/timer.d.ts +2 -1
- package/dist/timer.js +5 -3
- package/dist/wavesurfer.d.ts +131 -0
- package/dist/wavesurfer.js +209 -0
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +50 -20
- package/dist/index.d.ts +0 -92
- package/dist/index.js +0 -166
- package/dist/player-webaudio.d.ts +0 -8
- package/dist/player-webaudio.js +0 -30
- package/dist/wavesurfer.Regions.min.js +0 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import Fetcher from './fetcher.js';
|
|
2
|
+
import Decoder from './decoder.js';
|
|
3
|
+
import Renderer from './renderer.js';
|
|
4
|
+
import Player from './player.js';
|
|
5
|
+
import Timer from './timer.js';
|
|
6
|
+
const defaultOptions = {
|
|
7
|
+
height: 128,
|
|
8
|
+
waveColor: '#999',
|
|
9
|
+
progressColor: '#555',
|
|
10
|
+
cursorWidth: 1,
|
|
11
|
+
minPxPerSec: 0,
|
|
12
|
+
fillParent: true,
|
|
13
|
+
interact: true,
|
|
14
|
+
autoCenter: true,
|
|
15
|
+
};
|
|
16
|
+
class WaveSurfer extends Player {
|
|
17
|
+
options;
|
|
18
|
+
fetcher;
|
|
19
|
+
decoder;
|
|
20
|
+
renderer;
|
|
21
|
+
timer;
|
|
22
|
+
plugins = [];
|
|
23
|
+
decodedData = null;
|
|
24
|
+
canPlay = false;
|
|
25
|
+
/** Create a new WaveSurfer instance */
|
|
26
|
+
static create(options) {
|
|
27
|
+
return new WaveSurfer(options);
|
|
28
|
+
}
|
|
29
|
+
/** Create a new WaveSurfer instance */
|
|
30
|
+
constructor(options) {
|
|
31
|
+
super({
|
|
32
|
+
media: options.media,
|
|
33
|
+
autoplay: options.autoplay,
|
|
34
|
+
playbackRate: options.audioRate,
|
|
35
|
+
});
|
|
36
|
+
this.options = Object.assign({}, defaultOptions, options);
|
|
37
|
+
this.fetcher = new Fetcher();
|
|
38
|
+
this.decoder = new Decoder();
|
|
39
|
+
this.timer = new Timer();
|
|
40
|
+
this.renderer = new Renderer({
|
|
41
|
+
container: this.options.container,
|
|
42
|
+
}, this.options);
|
|
43
|
+
this.initPlayerEvents();
|
|
44
|
+
this.initRendererEvents();
|
|
45
|
+
this.initTimerEvents();
|
|
46
|
+
this.initReadyEvent();
|
|
47
|
+
this.initPlugins();
|
|
48
|
+
const url = this.options.url || this.options.media?.src;
|
|
49
|
+
if (url) {
|
|
50
|
+
this.load(url, this.options.peaks, this.options.duration);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
setOptions(options) {
|
|
54
|
+
this.options = { ...this.options, ...options };
|
|
55
|
+
this.renderer.setOptions(this.options);
|
|
56
|
+
}
|
|
57
|
+
initPlayerEvents() {
|
|
58
|
+
this.subscriptions.push(this.onMediaEvent('timeupdate', () => {
|
|
59
|
+
const currentTime = this.getCurrentTime();
|
|
60
|
+
this.renderer.renderProgress(currentTime / this.getDuration(), this.isPlaying());
|
|
61
|
+
this.emit('timeupdate', { currentTime });
|
|
62
|
+
}), this.onMediaEvent('play', () => {
|
|
63
|
+
this.emit('play');
|
|
64
|
+
this.timer.start();
|
|
65
|
+
}), this.onMediaEvent('pause', () => {
|
|
66
|
+
this.emit('pause');
|
|
67
|
+
this.timer.stop();
|
|
68
|
+
if (this.getCurrentTime() >= this.getDuration()) {
|
|
69
|
+
this.emit('finish');
|
|
70
|
+
}
|
|
71
|
+
}), this.onMediaEvent('canplay', () => {
|
|
72
|
+
this.canPlay = true;
|
|
73
|
+
this.emit('canplay', { duration: this.getDuration() });
|
|
74
|
+
}), this.onMediaEvent('seeking', () => {
|
|
75
|
+
this.emit('seeking', { currentTime: this.getCurrentTime() });
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
initRendererEvents() {
|
|
79
|
+
// Seek on click
|
|
80
|
+
this.subscriptions.push(this.renderer.on('click', ({ relativeX }) => {
|
|
81
|
+
if (this.options.interact) {
|
|
82
|
+
this.seekTo(relativeX);
|
|
83
|
+
this.emit('interaction');
|
|
84
|
+
}
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
initTimerEvents() {
|
|
88
|
+
// The timer fires every 16ms for a smooth progress animation
|
|
89
|
+
this.subscriptions.push(this.timer.on('tick', () => {
|
|
90
|
+
const currentTime = this.getCurrentTime();
|
|
91
|
+
this.renderer.renderProgress(currentTime / this.getDuration(), true);
|
|
92
|
+
this.emit('timeupdate', { currentTime });
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
initReadyEvent() {
|
|
96
|
+
const emitReady = () => {
|
|
97
|
+
if (this.decodedData && this.canPlay) {
|
|
98
|
+
this.emit('ready', { duration: this.getDuration() });
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
this.subscriptions.push(this.on('decode', emitReady), this.on('canplay', emitReady));
|
|
102
|
+
}
|
|
103
|
+
initPlugins() {
|
|
104
|
+
if (!this.options.plugins?.length)
|
|
105
|
+
return;
|
|
106
|
+
this.options.plugins.forEach((plugin) => {
|
|
107
|
+
this.registerPlugin(plugin);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** Register a wavesurfer.js plugin */
|
|
111
|
+
registerPlugin(plugin) {
|
|
112
|
+
plugin.init({
|
|
113
|
+
wavesurfer: this,
|
|
114
|
+
container: this.renderer.getContainer(),
|
|
115
|
+
wrapper: this.renderer.getWrapper(),
|
|
116
|
+
});
|
|
117
|
+
this.plugins.push(plugin);
|
|
118
|
+
return plugin;
|
|
119
|
+
}
|
|
120
|
+
/** Load an audio file by URL, with optional pre-decoded audio data */
|
|
121
|
+
async load(url, channelData, duration) {
|
|
122
|
+
this.decodedData = null;
|
|
123
|
+
this.canPlay = false;
|
|
124
|
+
this.loadUrl(url);
|
|
125
|
+
// Fetch and decode the audio of no pre-computed audio data is provided
|
|
126
|
+
if (channelData == null) {
|
|
127
|
+
const audio = await this.fetcher.load(url);
|
|
128
|
+
const data = await this.decoder.decode(audio);
|
|
129
|
+
this.decodedData = data;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
if (!duration) {
|
|
133
|
+
duration =
|
|
134
|
+
(await new Promise((resolve) => {
|
|
135
|
+
this.onceMediaEvent('loadedmetadata', () => resolve(this.getDuration()));
|
|
136
|
+
})) || 0;
|
|
137
|
+
}
|
|
138
|
+
// Allow a single array of numbers
|
|
139
|
+
if (typeof channelData[0] === 'number')
|
|
140
|
+
channelData = [channelData];
|
|
141
|
+
this.decodedData = {
|
|
142
|
+
duration,
|
|
143
|
+
numberOfChannels: channelData.length,
|
|
144
|
+
sampleRate: channelData[0].length / duration,
|
|
145
|
+
getChannelData: (i) => channelData?.[i],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
this.renderer.render(this.decodedData);
|
|
149
|
+
this.emit('decode', { duration: this.decodedData.duration });
|
|
150
|
+
}
|
|
151
|
+
/** Zoom in or out */
|
|
152
|
+
zoom(minPxPerSec) {
|
|
153
|
+
if (!this.decodedData) {
|
|
154
|
+
throw new Error('No audio loaded');
|
|
155
|
+
}
|
|
156
|
+
this.renderer.zoom(minPxPerSec);
|
|
157
|
+
this.emit('zoom', { minPxPerSec });
|
|
158
|
+
}
|
|
159
|
+
/** Get the decoded audio data */
|
|
160
|
+
getDecodedData() {
|
|
161
|
+
return this.decodedData;
|
|
162
|
+
}
|
|
163
|
+
getDuration() {
|
|
164
|
+
const audioDuration = this.getMediaElement()?.duration;
|
|
165
|
+
return audioDuration > 0 && audioDuration < Infinity ? audioDuration : this.decodedData?.duration || 0;
|
|
166
|
+
}
|
|
167
|
+
/** Toggle if the waveform should react to clicks */
|
|
168
|
+
toggleInteraction(isInteractive) {
|
|
169
|
+
this.options.interact = isInteractive;
|
|
170
|
+
}
|
|
171
|
+
/** Seeks to a percentage of audio as [0..1] (0 = beginning, 1 = end) */
|
|
172
|
+
seekTo(progress) {
|
|
173
|
+
const time = this.getDuration() * progress;
|
|
174
|
+
this.setTime(time);
|
|
175
|
+
}
|
|
176
|
+
/** Play or pause the audio */
|
|
177
|
+
playPause() {
|
|
178
|
+
if (this.isPlaying()) {
|
|
179
|
+
this.pause();
|
|
180
|
+
return Promise.resolve();
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
return this.play();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** Stop the audio and go to the beginning */
|
|
187
|
+
stop() {
|
|
188
|
+
this.pause();
|
|
189
|
+
this.setTime(0);
|
|
190
|
+
}
|
|
191
|
+
/** Skip N or -N seconds from the current positions */
|
|
192
|
+
skip(seconds) {
|
|
193
|
+
this.setTime(this.getCurrentTime() + seconds);
|
|
194
|
+
}
|
|
195
|
+
/** Empty the waveform by loading a tiny silent audio */
|
|
196
|
+
empty() {
|
|
197
|
+
this.load('', [[0]], 0.001);
|
|
198
|
+
}
|
|
199
|
+
/** Unmount wavesurfer */
|
|
200
|
+
destroy() {
|
|
201
|
+
this.emit('destroy');
|
|
202
|
+
this.plugins.forEach((plugin) => plugin.destroy());
|
|
203
|
+
this.timer.destroy();
|
|
204
|
+
this.decoder.destroy();
|
|
205
|
+
this.renderer.destroy();
|
|
206
|
+
super.destroy();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export default WaveSurfer;
|
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 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})()));
|
|
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})()));
|
package/package.json
CHANGED
|
@@ -1,33 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wavesurfer.js",
|
|
3
|
-
"version": "7.0.0-alpha.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
3
|
+
"version": "7.0.0-alpha.30",
|
|
4
|
+
"license": "BSD-3-Clause",
|
|
5
|
+
"author": "katspaugh",
|
|
6
|
+
"homepage": "https://wavesurfer-js.org",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"waveform",
|
|
9
|
+
"audio",
|
|
10
|
+
"player",
|
|
11
|
+
"music",
|
|
12
|
+
"linguistics"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"types": "./dist/wavesurfer.d.ts",
|
|
19
|
+
"main": "./dist/wavesurfer.js",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"import": "./dist/wavesurfer.js",
|
|
23
|
+
"require": "./dist/wavesurfer.min.js"
|
|
24
|
+
},
|
|
25
|
+
"./dist/plugins/*": {
|
|
26
|
+
"import": "./dist/plugins/*.js",
|
|
27
|
+
"require": "./dist/plugins/*.min.js"
|
|
28
|
+
},
|
|
29
|
+
"./dist/plugins/*.js": {
|
|
30
|
+
"import": "./dist/plugins/*.js",
|
|
31
|
+
"require": "./dist/plugins/*.min.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
8
34
|
"scripts": {
|
|
9
35
|
"build:dev": "tsc -w",
|
|
10
36
|
"build:umd": "webpack && webpack --config webpack.config.plugins.js",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
37
|
+
"build": "tsc && npm run build:umd",
|
|
38
|
+
"prepublishOnly": "npm run build",
|
|
39
|
+
"docs": "typedoc src/wavesurfer.ts src/plugins/*",
|
|
13
40
|
"lint": "eslint --ext .ts src --fix",
|
|
14
|
-
"prettier": "prettier -w
|
|
15
|
-
"
|
|
16
|
-
"
|
|
41
|
+
"prettier": "prettier -w '**/*.{js,ts,css}' --ignore-path .gitignore",
|
|
42
|
+
"cypress": "cypress open",
|
|
43
|
+
"test": "cypress run",
|
|
44
|
+
"serve": "browser-sync start --server --port 9090 --startPath /tutorial --watch '*'",
|
|
45
|
+
"start": "npm run build:dev & npm run serve"
|
|
17
46
|
},
|
|
18
|
-
"author": "katspaugh <katspaugh@gmail.com>",
|
|
19
|
-
"license": "ISC",
|
|
20
47
|
"devDependencies": {
|
|
21
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
22
|
-
"@typescript-eslint/parser": "^5.
|
|
23
|
-
"
|
|
24
|
-
"
|
|
48
|
+
"@typescript-eslint/eslint-plugin": "^5.57.0",
|
|
49
|
+
"@typescript-eslint/parser": "^5.57.0",
|
|
50
|
+
"browser-sync": "^2.29.0",
|
|
51
|
+
"cypress": "^12.9.0",
|
|
52
|
+
"eslint": "^8.37.0",
|
|
53
|
+
"eslint-config-prettier": "^8.8.0",
|
|
25
54
|
"eslint-plugin-prettier": "^4.2.1",
|
|
26
|
-
"prettier": "^2.8.
|
|
55
|
+
"prettier": "^2.8.7",
|
|
27
56
|
"ts-loader": "^9.4.2",
|
|
28
|
-
"typedoc": "^0.23.
|
|
29
|
-
"
|
|
30
|
-
"
|
|
57
|
+
"typedoc": "^0.23.28",
|
|
58
|
+
"typedoc-plugin-rename-defaults": "^0.6.5",
|
|
59
|
+
"typescript": "^5.0.4",
|
|
60
|
+
"webpack": "^5.77.0",
|
|
31
61
|
"webpack-cli": "^5.0.1"
|
|
32
62
|
},
|
|
33
63
|
"dependencies": {}
|
package/dist/index.d.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
|
|
2
|
-
import BasePlugin from './base-plugin.js';
|
|
3
|
-
export declare enum PlayerType {
|
|
4
|
-
WebAudio = "WebAudio",
|
|
5
|
-
MediaElement = "MediaElement"
|
|
6
|
-
}
|
|
7
|
-
export type WaveSurferOptions = {
|
|
8
|
-
/** HTML element or CSS selector */
|
|
9
|
-
container: HTMLElement | string | null;
|
|
10
|
-
/** Height of the waveform in pixels */
|
|
11
|
-
height?: number;
|
|
12
|
-
/** The color of the waveform */
|
|
13
|
-
waveColor?: string;
|
|
14
|
-
/** The color of the progress mask */
|
|
15
|
-
progressColor?: string;
|
|
16
|
-
/** If set, the waveform will be rendered in bars like so: ▁ ▂ ▇ ▃ ▅ ▂ */
|
|
17
|
-
barWidth?: number;
|
|
18
|
-
/** Spacing between bars in pixels */
|
|
19
|
-
barGap?: number;
|
|
20
|
-
/** Rounded borders for bars */
|
|
21
|
-
barRadius?: number;
|
|
22
|
-
/** Minimum pixels per second of audio (zoom) */
|
|
23
|
-
minPxPerSec?: number;
|
|
24
|
-
/** Audio URL */
|
|
25
|
-
url?: string;
|
|
26
|
-
/** Pre-computed audio data */
|
|
27
|
-
channelData?: Float32Array[];
|
|
28
|
-
/** Pre-computed duration */
|
|
29
|
-
duration?: number;
|
|
30
|
-
/** Player "backend", the default is MediaElement */
|
|
31
|
-
backend?: PlayerType;
|
|
32
|
-
/** Use an existing media element instead of creating one */
|
|
33
|
-
media?: HTMLMediaElement;
|
|
34
|
-
};
|
|
35
|
-
export type WaveSurferEvents = {
|
|
36
|
-
ready: {
|
|
37
|
-
duration: number;
|
|
38
|
-
};
|
|
39
|
-
canplay: void;
|
|
40
|
-
play: void;
|
|
41
|
-
pause: void;
|
|
42
|
-
audioprocess: {
|
|
43
|
-
currentTime: number;
|
|
44
|
-
};
|
|
45
|
-
seek: {
|
|
46
|
-
time: number;
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
export type WaveSurferPluginParams = {
|
|
50
|
-
wavesurfer: WaveSurfer;
|
|
51
|
-
renderer: WaveSurfer['renderer'];
|
|
52
|
-
};
|
|
53
|
-
export declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
|
|
54
|
-
private options;
|
|
55
|
-
private fetcher;
|
|
56
|
-
private decoder;
|
|
57
|
-
private renderer;
|
|
58
|
-
private player;
|
|
59
|
-
private timer;
|
|
60
|
-
private plugins;
|
|
61
|
-
private subscriptions;
|
|
62
|
-
private channelData;
|
|
63
|
-
private duration;
|
|
64
|
-
/** Create a new WaveSurfer instance */
|
|
65
|
-
static create(options: WaveSurferOptions): WaveSurfer;
|
|
66
|
-
/** Create a new WaveSurfer instance */
|
|
67
|
-
constructor(options: WaveSurferOptions);
|
|
68
|
-
private initPlayerEvents;
|
|
69
|
-
private initRendererEvents;
|
|
70
|
-
private initTimerEvents;
|
|
71
|
-
/** Unmount wavesurfer */
|
|
72
|
-
destroy(): void;
|
|
73
|
-
/** Load an audio file by URL */
|
|
74
|
-
load(url: string, channelData?: Float32Array[], duration?: number): Promise<void>;
|
|
75
|
-
/** Zoom in or out */
|
|
76
|
-
zoom(minPxPerSec: number): void;
|
|
77
|
-
/** Start playing the audio */
|
|
78
|
-
play(): void;
|
|
79
|
-
/** Pause the audio */
|
|
80
|
-
pause(): void;
|
|
81
|
-
/** Skip to a time position in seconds */
|
|
82
|
-
seekTo(time: number): void;
|
|
83
|
-
/** Check if the audio is playing */
|
|
84
|
-
isPlaying(): boolean;
|
|
85
|
-
/** Get the duration of the audio in seconds */
|
|
86
|
-
getDuration(): number;
|
|
87
|
-
/** Get the current audio position in seconds */
|
|
88
|
-
getCurrentTime(): number;
|
|
89
|
-
/** Register and initialize a plugin */
|
|
90
|
-
registerPlugin<T extends BasePlugin<GeneralEventTypes>>(CustomPlugin: new (params: WaveSurferPluginParams) => T): T;
|
|
91
|
-
}
|
|
92
|
-
export default WaveSurfer;
|
package/dist/index.js
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
import Fetcher from './fetcher.js';
|
|
11
|
-
import Decoder from './decoder.js';
|
|
12
|
-
import Renderer from './renderer.js';
|
|
13
|
-
import Player from './player.js';
|
|
14
|
-
import WebAudioPlayer from './player-webaudio.js';
|
|
15
|
-
import EventEmitter from './event-emitter.js';
|
|
16
|
-
import Timer from './timer.js';
|
|
17
|
-
export var PlayerType;
|
|
18
|
-
(function (PlayerType) {
|
|
19
|
-
PlayerType["WebAudio"] = "WebAudio";
|
|
20
|
-
PlayerType["MediaElement"] = "MediaElement";
|
|
21
|
-
})(PlayerType || (PlayerType = {}));
|
|
22
|
-
const defaultOptions = {
|
|
23
|
-
height: 128,
|
|
24
|
-
waveColor: '#999',
|
|
25
|
-
progressColor: '#555',
|
|
26
|
-
minPxPerSec: 0,
|
|
27
|
-
backend: 'MediaElement',
|
|
28
|
-
};
|
|
29
|
-
export class WaveSurfer extends EventEmitter {
|
|
30
|
-
/** Create a new WaveSurfer instance */
|
|
31
|
-
static create(options) {
|
|
32
|
-
return new WaveSurfer(options);
|
|
33
|
-
}
|
|
34
|
-
/** Create a new WaveSurfer instance */
|
|
35
|
-
constructor(options) {
|
|
36
|
-
var _a;
|
|
37
|
-
super();
|
|
38
|
-
this.plugins = [];
|
|
39
|
-
this.subscriptions = [];
|
|
40
|
-
this.channelData = null;
|
|
41
|
-
this.duration = 0;
|
|
42
|
-
this.options = Object.assign({}, defaultOptions, options);
|
|
43
|
-
this.fetcher = new Fetcher();
|
|
44
|
-
this.decoder = new Decoder();
|
|
45
|
-
this.timer = new Timer();
|
|
46
|
-
this.player = new (this.options.backend === PlayerType.WebAudio ? WebAudioPlayer : Player)({
|
|
47
|
-
media: this.options.media,
|
|
48
|
-
});
|
|
49
|
-
this.renderer = new Renderer({
|
|
50
|
-
container: this.options.container,
|
|
51
|
-
height: this.options.height,
|
|
52
|
-
waveColor: this.options.waveColor,
|
|
53
|
-
progressColor: this.options.progressColor,
|
|
54
|
-
minPxPerSec: this.options.minPxPerSec,
|
|
55
|
-
barWidth: this.options.barWidth,
|
|
56
|
-
barGap: this.options.barGap,
|
|
57
|
-
barRadius: this.options.barRadius,
|
|
58
|
-
});
|
|
59
|
-
this.initPlayerEvents();
|
|
60
|
-
this.initRendererEvents();
|
|
61
|
-
this.initTimerEvents();
|
|
62
|
-
const url = this.options.url || ((_a = this.options.media) === null || _a === void 0 ? void 0 : _a.src);
|
|
63
|
-
if (url) {
|
|
64
|
-
this.load(url, this.options.channelData, this.options.duration);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
initPlayerEvents() {
|
|
68
|
-
this.subscriptions.push(this.player.on('timeupdate', () => {
|
|
69
|
-
const currentTime = this.getCurrentTime();
|
|
70
|
-
this.renderer.renderProgress(currentTime / this.duration, this.isPlaying());
|
|
71
|
-
this.emit('audioprocess', { currentTime });
|
|
72
|
-
}), this.player.on('play', () => {
|
|
73
|
-
this.emit('play');
|
|
74
|
-
}), this.player.on('pause', () => {
|
|
75
|
-
this.emit('pause');
|
|
76
|
-
}), this.player.on('canplay', () => {
|
|
77
|
-
this.emit('canplay');
|
|
78
|
-
}), this.player.on('seeking', () => {
|
|
79
|
-
this.emit('seek', { time: this.getCurrentTime() });
|
|
80
|
-
}));
|
|
81
|
-
}
|
|
82
|
-
initRendererEvents() {
|
|
83
|
-
// Seek on click
|
|
84
|
-
this.subscriptions.push(this.renderer.on('click', ({ relativeX }) => {
|
|
85
|
-
const time = this.getDuration() * relativeX;
|
|
86
|
-
this.seekTo(time);
|
|
87
|
-
}));
|
|
88
|
-
}
|
|
89
|
-
initTimerEvents() {
|
|
90
|
-
// The timer fires every 16ms for a smooth progress animation
|
|
91
|
-
this.subscriptions.push(this.timer.on('tick', () => {
|
|
92
|
-
if (this.isPlaying()) {
|
|
93
|
-
const currentTime = this.getCurrentTime();
|
|
94
|
-
this.renderer.renderProgress(currentTime / this.duration, true);
|
|
95
|
-
this.emit('audioprocess', { currentTime });
|
|
96
|
-
}
|
|
97
|
-
}));
|
|
98
|
-
}
|
|
99
|
-
/** Unmount wavesurfer */
|
|
100
|
-
destroy() {
|
|
101
|
-
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
102
|
-
this.plugins.forEach((plugin) => plugin.destroy());
|
|
103
|
-
this.timer.destroy();
|
|
104
|
-
this.player.destroy();
|
|
105
|
-
this.decoder.destroy();
|
|
106
|
-
this.renderer.destroy();
|
|
107
|
-
}
|
|
108
|
-
/** Load an audio file by URL */
|
|
109
|
-
load(url, channelData, duration) {
|
|
110
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
111
|
-
this.player.loadUrl(url);
|
|
112
|
-
// Fetch and decode the audio of no pre-computed audio data is provided
|
|
113
|
-
if (channelData == null || duration == null) {
|
|
114
|
-
const audio = yield this.fetcher.load(url);
|
|
115
|
-
const data = yield this.decoder.decode(audio);
|
|
116
|
-
channelData = data.channelData;
|
|
117
|
-
duration = data.duration;
|
|
118
|
-
}
|
|
119
|
-
this.channelData = channelData;
|
|
120
|
-
this.duration = duration;
|
|
121
|
-
this.renderer.render(this.channelData, this.duration);
|
|
122
|
-
this.emit('ready', { duration: this.duration });
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
/** Zoom in or out */
|
|
126
|
-
zoom(minPxPerSec) {
|
|
127
|
-
if (this.channelData == null || this.duration == null) {
|
|
128
|
-
throw new Error('No audio loaded');
|
|
129
|
-
}
|
|
130
|
-
this.renderer.zoom(this.channelData, this.duration, minPxPerSec);
|
|
131
|
-
}
|
|
132
|
-
/** Start playing the audio */
|
|
133
|
-
play() {
|
|
134
|
-
this.player.play();
|
|
135
|
-
}
|
|
136
|
-
/** Pause the audio */
|
|
137
|
-
pause() {
|
|
138
|
-
this.player.pause();
|
|
139
|
-
}
|
|
140
|
-
/** Skip to a time position in seconds */
|
|
141
|
-
seekTo(time) {
|
|
142
|
-
this.player.seekTo(time);
|
|
143
|
-
}
|
|
144
|
-
/** Check if the audio is playing */
|
|
145
|
-
isPlaying() {
|
|
146
|
-
return this.player.isPlaying();
|
|
147
|
-
}
|
|
148
|
-
/** Get the duration of the audio in seconds */
|
|
149
|
-
getDuration() {
|
|
150
|
-
return this.duration;
|
|
151
|
-
}
|
|
152
|
-
/** Get the current audio position in seconds */
|
|
153
|
-
getCurrentTime() {
|
|
154
|
-
return this.player.getCurrentTime();
|
|
155
|
-
}
|
|
156
|
-
/** Register and initialize a plugin */
|
|
157
|
-
registerPlugin(CustomPlugin) {
|
|
158
|
-
const plugin = new CustomPlugin({
|
|
159
|
-
wavesurfer: this,
|
|
160
|
-
renderer: this.renderer,
|
|
161
|
-
});
|
|
162
|
-
this.plugins.push(plugin);
|
|
163
|
-
return plugin;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
export default WaveSurfer;
|
package/dist/player-webaudio.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import Player from './player.js';
|
|
2
|
-
class WebAudioPlayer extends Player {
|
|
3
|
-
constructor() {
|
|
4
|
-
super(...arguments);
|
|
5
|
-
this.audioCtx = null;
|
|
6
|
-
this.sourceNode = null;
|
|
7
|
-
}
|
|
8
|
-
destroy() {
|
|
9
|
-
var _a, _b;
|
|
10
|
-
(_a = this.sourceNode) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
11
|
-
this.sourceNode = null;
|
|
12
|
-
(_b = this.audioCtx) === null || _b === void 0 ? void 0 : _b.close();
|
|
13
|
-
this.audioCtx = null;
|
|
14
|
-
super.destroy();
|
|
15
|
-
}
|
|
16
|
-
loadUrl(url) {
|
|
17
|
-
super.loadUrl(url);
|
|
18
|
-
if (!this.audioCtx) {
|
|
19
|
-
this.audioCtx = new AudioContext({
|
|
20
|
-
latencyHint: 'playback',
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
if (this.sourceNode) {
|
|
24
|
-
this.sourceNode.disconnect();
|
|
25
|
-
}
|
|
26
|
-
this.sourceNode = this.audioCtx.createMediaElementSource(this.media);
|
|
27
|
-
this.sourceNode.connect(this.audioCtx.destination);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
export default WebAudioPlayer;
|
|
@@ -1 +0,0 @@
|
|
|
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})()));
|