wavesurfer.js 7.0.0-beta.5 → 7.0.0-beta.6

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.
Files changed (52) hide show
  1. package/README.md +1 -5
  2. package/dist/cypress/support/commands.d.ts +1 -0
  3. package/dist/cypress/support/commands.js +39 -0
  4. package/dist/cypress/support/e2e.d.ts +1 -0
  5. package/dist/cypress/support/e2e.js +18 -0
  6. package/dist/cypress.config.d.ts +2 -0
  7. package/dist/cypress.config.js +10 -0
  8. package/dist/decoder.d.ts +1 -1
  9. package/dist/draggable.d.ts +1 -0
  10. package/dist/draggable.js +57 -0
  11. package/dist/plugins/envelope.js +4 -25
  12. package/dist/plugins/envelope.min.js +1 -1
  13. package/dist/plugins/minimap.min.js +1 -1
  14. package/dist/plugins/regions.js +13 -51
  15. package/dist/plugins/regions.min.js +1 -1
  16. package/dist/plugins/timeline.min.js +1 -1
  17. package/dist/renderer.d.ts +1 -0
  18. package/dist/renderer.js +64 -66
  19. package/dist/src/base-plugin.d.ts +13 -0
  20. package/dist/src/base-plugin.js +22 -0
  21. package/dist/src/decoder.d.ts +9 -0
  22. package/dist/src/decoder.js +48 -0
  23. package/dist/src/event-emitter.d.ts +19 -0
  24. package/dist/src/event-emitter.js +45 -0
  25. package/dist/src/fetcher.d.ts +5 -0
  26. package/dist/src/fetcher.js +7 -0
  27. package/dist/src/player.d.ts +45 -0
  28. package/dist/src/player.js +114 -0
  29. package/dist/src/plugins/envelope.d.ts +71 -0
  30. package/dist/src/plugins/envelope.js +350 -0
  31. package/dist/src/plugins/minimap.d.ts +39 -0
  32. package/dist/src/plugins/minimap.js +117 -0
  33. package/dist/src/plugins/record.d.ts +26 -0
  34. package/dist/src/plugins/record.js +124 -0
  35. package/dist/src/plugins/regions.d.ts +93 -0
  36. package/dist/src/plugins/regions.js +395 -0
  37. package/dist/src/plugins/spectrogram-fft.d.ts +9 -0
  38. package/dist/src/plugins/spectrogram-fft.js +150 -0
  39. package/dist/src/plugins/spectrogram.d.ts +69 -0
  40. package/dist/src/plugins/spectrogram.js +340 -0
  41. package/dist/src/plugins/timeline.d.ts +45 -0
  42. package/dist/src/plugins/timeline.js +162 -0
  43. package/dist/src/renderer.d.ts +41 -0
  44. package/dist/src/renderer.js +420 -0
  45. package/dist/src/timer.d.ts +11 -0
  46. package/dist/src/timer.js +19 -0
  47. package/dist/src/wavesurfer.d.ts +149 -0
  48. package/dist/src/wavesurfer.js +229 -0
  49. package/dist/wavesurfer.d.ts +5 -1
  50. package/dist/wavesurfer.js +2 -8
  51. package/dist/wavesurfer.min.js +1 -1
  52. package/package.json +4 -1
@@ -0,0 +1,229 @@
1
+ import Decoder from './decoder.js';
2
+ import Fetcher from './fetcher.js';
3
+ import Player from './player.js';
4
+ import Renderer from './renderer.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
+ autoScroll: true,
15
+ autoCenter: true,
16
+ sampleRate: 8000,
17
+ };
18
+ class WaveSurfer extends Player {
19
+ options;
20
+ renderer;
21
+ timer;
22
+ plugins = [];
23
+ decodedData = null;
24
+ subscriptions = [];
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.timer = new Timer();
38
+ this.renderer = new Renderer(this.options);
39
+ this.initPlayerEvents();
40
+ this.initRendererEvents();
41
+ this.initTimerEvents();
42
+ this.initPlugins();
43
+ const url = this.options.url || this.options.media?.currentSrc || this.options.media?.src;
44
+ if (url) {
45
+ this.load(url, this.options.peaks, this.options.duration);
46
+ }
47
+ }
48
+ setOptions(options) {
49
+ this.options = { ...this.options, ...options };
50
+ this.renderer.setOptions(this.options);
51
+ if (options.audioRate) {
52
+ this.setPlaybackRate(options.audioRate);
53
+ }
54
+ }
55
+ initPlayerEvents() {
56
+ this.subscriptions.push(this.onMediaEvent('timeupdate', () => {
57
+ const currentTime = this.getCurrentTime();
58
+ this.renderer.renderProgress(currentTime / this.getDuration(), this.isPlaying());
59
+ this.emit('timeupdate', currentTime);
60
+ }), this.onMediaEvent('play', () => {
61
+ this.emit('play');
62
+ this.timer.start();
63
+ }), this.onMediaEvent('pause', () => {
64
+ this.emit('pause');
65
+ this.timer.stop();
66
+ }), this.onMediaEvent('ended', () => {
67
+ this.emit('finish');
68
+ }), this.onMediaEvent('seeking', () => {
69
+ this.emit('seeking', this.getCurrentTime());
70
+ }));
71
+ }
72
+ initRendererEvents() {
73
+ this.subscriptions.push(
74
+ // Seek on click
75
+ this.renderer.on('click', (relativeX) => {
76
+ if (this.options.interact) {
77
+ this.seekTo(relativeX);
78
+ this.emit('interaction');
79
+ this.emit('click', relativeX);
80
+ }
81
+ }),
82
+ // Scroll
83
+ this.renderer.on('scroll', (startX, endX) => {
84
+ const duration = this.getDuration();
85
+ this.emit('scroll', startX * duration, endX * duration);
86
+ }),
87
+ // Redraw
88
+ this.renderer.on('render', () => {
89
+ this.emit('redraw');
90
+ }));
91
+ // Drag
92
+ {
93
+ let debounce;
94
+ this.subscriptions.push(this.renderer.on('drag', (relativeX) => {
95
+ if (!this.options.interact)
96
+ return;
97
+ // Update the visual position
98
+ this.renderer.renderProgress(relativeX);
99
+ // Set the audio position with a debounce
100
+ clearTimeout(debounce);
101
+ debounce = setTimeout(() => {
102
+ this.seekTo(relativeX);
103
+ }, this.isPlaying() ? 0 : 200);
104
+ this.emit('interaction');
105
+ this.emit('drag', relativeX);
106
+ }));
107
+ }
108
+ }
109
+ initTimerEvents() {
110
+ // The timer fires every 16ms for a smooth progress animation
111
+ this.subscriptions.push(this.timer.on('tick', () => {
112
+ const currentTime = this.getCurrentTime();
113
+ this.renderer.renderProgress(currentTime / this.getDuration(), true);
114
+ this.emit('timeupdate', currentTime);
115
+ this.emit('audioprocess', currentTime);
116
+ }));
117
+ }
118
+ initPlugins() {
119
+ if (!this.options.plugins?.length)
120
+ return;
121
+ this.options.plugins.forEach((plugin) => {
122
+ this.registerPlugin(plugin);
123
+ });
124
+ }
125
+ /** Register a wavesurfer.js plugin */
126
+ registerPlugin(plugin) {
127
+ plugin.init(this);
128
+ this.plugins.push(plugin);
129
+ return plugin;
130
+ }
131
+ /** For plugins only: get the waveform wrapper div */
132
+ getWrapper() {
133
+ return this.renderer.getWrapper();
134
+ }
135
+ /** Get the current scroll position in pixels */
136
+ getScroll() {
137
+ return this.renderer.getScroll();
138
+ }
139
+ /** Get all registered plugins */
140
+ getActivePlugins() {
141
+ return this.plugins;
142
+ }
143
+ /** Load an audio file by URL, with optional pre-decoded audio data */
144
+ async load(url, channelData, duration) {
145
+ this.decodedData = null;
146
+ this.emit('load', url);
147
+ if (channelData) {
148
+ // Set the mediaelement source to the URL
149
+ this.setSrc(url);
150
+ // Pre-decoded audio data
151
+ if (!duration) {
152
+ // Wait for the audio duration
153
+ duration =
154
+ (await new Promise((resolve) => {
155
+ this.onceMediaEvent('loadedmetadata', () => resolve(this.getMediaElement().duration));
156
+ })) || 0;
157
+ }
158
+ this.decodedData = Decoder.createBuffer(channelData, duration);
159
+ }
160
+ else {
161
+ // Fetch and decode the audio of no pre-computed audio data is provided
162
+ const audio = await Fetcher.fetchArrayBuffer(url);
163
+ this.setSrc(url);
164
+ this.decodedData = await Decoder.decode(audio, this.options.sampleRate);
165
+ }
166
+ this.emit('decode', this.getDuration());
167
+ this.emit('ready', this.getDuration());
168
+ this.renderer.render(this.decodedData);
169
+ }
170
+ /** Zoom in or out */
171
+ zoom(minPxPerSec) {
172
+ if (!this.decodedData) {
173
+ throw new Error('No audio loaded');
174
+ }
175
+ this.renderer.zoom(minPxPerSec);
176
+ this.emit('zoom', minPxPerSec);
177
+ }
178
+ /** Get the decoded audio data */
179
+ getDecodedData() {
180
+ return this.decodedData;
181
+ }
182
+ /** Get the duration of the audio in seconds */
183
+ getDuration() {
184
+ const audioDuration = super.getDuration();
185
+ return audioDuration > 0 && audioDuration < Infinity ? audioDuration : this.decodedData?.duration || 0;
186
+ }
187
+ /** Toggle if the waveform should react to clicks */
188
+ toggleInteraction(isInteractive) {
189
+ this.options.interact = isInteractive;
190
+ }
191
+ /** Seeks to a percentage of audio as [0..1] (0 = beginning, 1 = end) */
192
+ seekTo(progress) {
193
+ const time = this.getDuration() * progress;
194
+ this.setTime(time);
195
+ }
196
+ /** Play or pause the audio */
197
+ playPause() {
198
+ if (this.isPlaying()) {
199
+ this.pause();
200
+ return Promise.resolve();
201
+ }
202
+ else {
203
+ return this.play();
204
+ }
205
+ }
206
+ /** Stop the audio and go to the beginning */
207
+ stop() {
208
+ this.pause();
209
+ this.setTime(0);
210
+ }
211
+ /** Skip N or -N seconds from the current positions */
212
+ skip(seconds) {
213
+ this.setTime(this.getCurrentTime() + seconds);
214
+ }
215
+ /** Empty the waveform by loading a tiny silent audio */
216
+ empty() {
217
+ this.load('', [[0]], 0.001);
218
+ }
219
+ /** Unmount wavesurfer */
220
+ destroy() {
221
+ this.emit('destroy');
222
+ this.subscriptions.forEach((unsubscribe) => unsubscribe());
223
+ this.plugins.forEach((plugin) => plugin.destroy());
224
+ this.timer.destroy();
225
+ this.renderer.destroy();
226
+ super.destroy();
227
+ }
228
+ }
229
+ export default WaveSurfer;
@@ -29,7 +29,7 @@ export type WaveSurferOptions = {
29
29
  /** Audio URL */
30
30
  url?: string;
31
31
  /** Pre-computed audio data */
32
- peaks?: Float32Array[] | Array<number[]>;
32
+ peaks?: Array<Float32Array | number[]>;
33
33
  /** Pre-computed duration */
34
34
  duration?: number;
35
35
  /** Use an existing media element instead of creating one */
@@ -50,8 +50,12 @@ export type WaveSurferOptions = {
50
50
  sampleRate?: number;
51
51
  /** Render each audio channel as a separate waveform */
52
52
  splitChannels?: WaveSurferOptions[];
53
+ /** Stretch the waveform to the full height */
54
+ normalize?: boolean;
53
55
  /** The list of plugins to initialize on start */
54
56
  plugins?: GenericPlugin[];
57
+ /** Custom render function */
58
+ renderFunction?: (peaks: Array<Float32Array | number[]>, ctx: CanvasRenderingContext2D) => void;
55
59
  };
56
60
  declare const defaultOptions: {
57
61
  height: number;
@@ -191,14 +191,8 @@ class WaveSurfer extends Player {
191
191
  this.setTime(time);
192
192
  }
193
193
  /** Play or pause the audio */
194
- playPause() {
195
- if (this.isPlaying()) {
196
- this.pause();
197
- return Promise.resolve();
198
- }
199
- else {
200
- return this.play();
201
- }
194
+ async playPause() {
195
+ return this.isPlaying() ? this.pause() : this.play();
202
196
  }
203
197
  /** Stop the audio and go to the beginning */
204
198
  stop() {
@@ -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:()=>d});const i={decode:async function(t,e){const i=new AudioContext({sampleRate:e}),s=i.decodeAudioData(t);return s.finally((()=>i.close())),s},createBuffer:function(t,e){return"number"==typeof t[0]&&(t=[t]),function(t){const e=t[0];if(e.some((t=>t>1||t<-1))){const i=e.length;let s=0;for(let t=0;t<i;t++){const i=Math.abs(e[t]);i>s&&(s=i)}for(const e of t)for(let t=0;t<i;t++)e[t]/=s}}(t),{duration:e,length:t[0].length,sampleRate:t[0].length/e,numberOfChannels:t.length,getChannelData:e=>t?.[e],copyFromChannel:AudioBuffer.prototype.copyFromChannel,copyToChannel:AudioBuffer.prototype.copyToChannel}}},s=async function(t){return fetch(t).then((t=>t.arrayBuffer()))},r=class{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const i=this.on(t,e),s=this.on(t,(()=>{i(),s()}));return i}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}},n=class extends r{constructor(t){super(),this.isExternalMedia=!1,t.media?(this.media=t.media,this.isExternalMedia=!0):this.media=document.createElement("audio"),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})}revokeSrc(){(this.media.currentSrc||this.media.src||"").startsWith("blob:")&&URL.revokeObjectURL(this.media.currentSrc)}setSrc(t,e){if((this.media.currentSrc||this.media.src||"")===t)return;this.revokeSrc();const i=e?URL.createObjectURL(new Blob([e],{type:"audio/wav"})):t;this.media.src=i}destroy(){this.media.pause(),this.revokeSrc(),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.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)}};class o extends r{constructor(t){let e;if(super(),this.timeouts=[],this.isScrolling=!1,this.audioData=null,this.resizeObserver=null,this.isDragging=!1,this.options=t,"string"==typeof t.container?e=document.querySelector(t.container):t.container instanceof HTMLElement&&(e=t.container),!e)throw new Error("Container not found");const[i,s]=this.initHtml();e.appendChild(i),this.container=i,this.scrollContainer=s.querySelector(".scroll"),this.wrapper=s.querySelector(".wrapper"),this.canvasWrapper=s.querySelector(".canvases"),this.progressWrapper=s.querySelector(".progress"),this.cursor=s.querySelector(".cursor"),this.initEvents()}initEvents(){this.wrapper.addEventListener("click",(t=>{const e=this.wrapper.getBoundingClientRect(),i=(t.clientX-e.left)/e.width;this.emit("click",i)})),this.initDrag(),this.scrollContainer.addEventListener("scroll",(()=>{const{scrollLeft:t,scrollWidth:e,clientWidth:i}=this.scrollContainer,s=t/e,r=(t+i)/e;this.emit("scroll",s,r)}));const t=this.createDelay(100);this.resizeObserver=new ResizeObserver((()=>{t((()=>this.reRender()))})),this.resizeObserver.observe(this.scrollContainer)}initDrag(){this.wrapper.addEventListener("mousedown",(t=>{const e=t.clientX,i=t=>{if(Math.abs(t.clientX-e)>=5){this.isDragging=!0;const e=this.wrapper.getBoundingClientRect();this.emit("drag",Math.max(0,Math.min(1,(t.clientX-e.left)/e.width)))}},s=()=>{document.removeEventListener("mousemove",i),document.removeEventListener("mouseup",s),this.isDragging=!1};document.addEventListener("mousemove",i),document.addEventListener("mouseup",s)}))}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 min-height: ${this.options.height}px;\n }\n :host .canvases > div {\n position: relative;\n }\n :host canvas {\n display: block;\n position: absolute;\n top: 0;\n image-rendering: pixelated;\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 }\n :host .progress > div {\n position: relative;\n }\n :host .cursor {\n pointer-events: none;\n position: absolute;\n z-index: 5;\n top: 0;\n left: 0;\n height: 100%;\n border-radius: 2px;\n }\n </style>\n\n <div class="scroll" part="scroll">\n <div class="wrapper">\n <div class="canvases"></div>\n <div class="progress"></div>\n <div class="cursor" part="cursor"></div>\n </div>\n </div>\n `,[t,e]}setOptions(t){this.options=t,this.reRender()}getWrapper(){return this.wrapper}getScroll(){return this.scrollContainer.scrollLeft}destroy(){this.container.remove(),this.resizeObserver?.disconnect()}createDelay(t=10){const e={};return this.timeouts.push(e),i=>{e.timeout&&clearTimeout(e.timeout),e.timeout=setTimeout(i,t)}}convertColorValues(t){if(!Array.isArray(t))return t||"";if(t.length<2)return 1===t.length?t[0]:"";const e=document.createElement("canvas"),i=e.getContext("2d").createLinearGradient(0,0,0,e.height),s=1/(t.length-1);return t.forEach(((t,e)=>{const r=e*s;i.addColorStop(r,t)})),i}renderSingleCanvas(t,e,i,s,r,n,o){const a=window.devicePixelRatio||1,h=e.height||0,l=null==e.barWidth||isNaN(e.barWidth)?1:e.barWidth*a,c=null==e.barGap||isNaN(e.barGap)?e.barWidth?l/2:0:e.barGap*a,d=e.barRadius||0,u=e.barHeight||1,p=1===t.length,m=t[0],g=p?m:t[1],f=p&&g.some((t=>t<0)),v=m.length,y=Math.floor(i/(l+c))/v,b=h/2;let C=0,w=0,S=0;const E=document.createElement("canvas");E.width=Math.round(i*(r-s)/v),E.height=h,E.style.width=`${Math.floor(E.width/a)}px`,E.style.height=`${e.height}px`,E.style.left=`${Math.floor(s*i/a/v)}px`,n.appendChild(E);const M=E.getContext("2d",{desynchronized:!0});M.beginPath(),M.fillStyle=this.convertColorValues(e.waveColor),M.roundRect||(M.roundRect=M.fillRect);for(let t=s;t<r;t++){const e=Math.round((t-s)*y);if(e>C){const t=Math.round(w*b*u),i=Math.round(S*b*u);M.roundRect(C*(l+c),b-t,l,t+(i||1),d),C=e,w=0,S=0}const i=f?m[t]:Math.abs(m[t]),r=f?g[t]:Math.abs(g[t]);i>w&&(w=i),(f?r<-S:r>S)&&(S=r<0?-r:r)}M.fill(),M.closePath();const D=E.cloneNode();o.appendChild(D);const R=D.getContext("2d",{desynchronized:!0});E.width>0&&E.height>0&&R.drawImage(E,0,0),R.globalCompositeOperation="source-in",R.fillStyle=this.convertColorValues(e.progressColor),R.fillRect(0,0,E.width,E.height)}renderWaveform(t,e,i){const s=document.createElement("div");s.style.height=`${e.height}px`,this.canvasWrapper.appendChild(s);const r=s.cloneNode();this.progressWrapper.appendChild(r);const{scrollLeft:n,scrollWidth:a,clientWidth:h}=this.scrollContainer,l=t[0].length,c=l/a,d=Math.min(o.MAX_CANVAS_WIDTH,h),u=Math.floor(Math.abs(n)*c),p=Math.ceil(u+d*c),m=p-u,g=(n,o)=>{this.renderSingleCanvas(t,e,i,Math.max(0,n),Math.min(o,l),s,r)},f=this.createDelay(),v=this.createDelay(),y=(t,e)=>{g(t,e),t>0&&f((()=>{y(t-m,e-m)}))},b=(t,e)=>{g(t,e),e<l&&v((()=>{b(t+m,e+m)}))};y(u,p),p<l&&b(p,p+m)}render(t){this.timeouts.forEach((t=>t.timeout&&clearTimeout(t.timeout))),this.timeouts=[];const e=window.devicePixelRatio||1,i=this.scrollContainer.clientWidth,s=Math.ceil(t.duration*(this.options.minPxPerSec||0));this.isScrolling=s>i;const r=this.options.fillParent&&!this.isScrolling,n=(r?i:s)*e;if(this.wrapper.style.width=r?"100%":`${s}px`,this.scrollContainer.style.overflowX=this.isScrolling?"auto":"hidden",this.scrollContainer.classList.toggle("noScrollbar",!!this.options.hideScrollbar),this.cursor.style.backgroundColor=`${this.options.cursorColor||this.options.progressColor}`,this.cursor.style.width=`${this.options.cursorWidth}px`,this.canvasWrapper.innerHTML="",this.progressWrapper.innerHTML="",this.options.splitChannels)for(let e=0;e<t.numberOfChannels;e++){const i={...this.options,...this.options.splitChannels[e]};this.renderWaveform([t.getChannelData(e)],i,n)}else{const e=[t.getChannelData(0)];t.numberOfChannels>1&&e.push(t.getChannelData(1)),this.renderWaveform(e,this.options,n)}this.audioData=t,this.emit("render")}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()}scrollIntoView(t,e=!1){const{clientWidth:i,scrollLeft:s,scrollWidth:r}=this.scrollContainer,n=r*t,o=i/2;if(n>s+(e&&this.options.autoCenter&&!this.isDragging?o:i)||n<s)if(this.options.autoCenter&&!this.isDragging){const t=o/20;n-(s+o)>=t&&n<s+i?this.scrollContainer.scrollLeft+=t:this.scrollContainer.scrollLeft=n-o}else if(this.isDragging){const t=10;this.scrollContainer.scrollLeft=n<s?n-t:n-i+t}else this.scrollContainer.scrollLeft=n;{const{scrollLeft:t}=this.scrollContainer,e=t/r,s=(t+i)/r;this.emit("scroll",e,s)}}renderProgress(t,e){isNaN(t)||(this.progressWrapper.style.width=100*t+"%",this.cursor.style.left=100*t+"%",this.cursor.style.marginLeft=100===Math.round(100*t)?`-${this.options.cursorWidth}px`:"",this.isScrolling&&this.options.autoScroll&&this.scrollIntoView(t,e))}}o.MAX_CANVAS_WIDTH=4e3;const a=o,h=class extends r{constructor(){super(...arguments),this.unsubscribe=()=>{}}start(){this.unsubscribe=this.on("tick",(()=>{requestAnimationFrame((()=>{this.emit("tick")}))})),this.emit("tick")}stop(){this.unsubscribe()}destroy(){this.unsubscribe()}},l={height:128,waveColor:"#999",progressColor:"#555",cursorWidth:1,minPxPerSec:0,fillParent:!0,interact:!0,autoScroll:!0,autoCenter:!0,sampleRate:8e3};class c extends n{static create(t){return new c(t)}constructor(t){super({media:t.media,autoplay:t.autoplay,playbackRate:t.audioRate}),this.plugins=[],this.decodedData=null,this.subscriptions=[],this.options=Object.assign({},l,t),this.timer=new h,this.renderer=new a(this.options),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents(),this.initPlugins();const e=this.options.url||this.options.media?.currentSrc||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),t.audioRate&&this.setPlaybackRate(t.audioRate)}initPlayerEvents(){this.subscriptions.push(this.onMediaEvent("timeupdate",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),this.isPlaying()),this.emit("timeupdate",t)})),this.onMediaEvent("play",(()=>{this.emit("play"),this.timer.start()})),this.onMediaEvent("pause",(()=>{this.emit("pause"),this.timer.stop()})),this.onMediaEvent("ended",(()=>{this.emit("finish")})),this.onMediaEvent("seeking",(()=>{this.emit("seeking",this.getCurrentTime())})))}initRendererEvents(){this.subscriptions.push(this.renderer.on("click",(t=>{this.options.interact&&(this.seekTo(t),this.emit("interaction"),this.emit("click",t))})),this.renderer.on("scroll",((t,e)=>{const i=this.getDuration();this.emit("scroll",t*i,e*i)})),this.renderer.on("render",(()=>{this.emit("redraw")})));{let t;this.subscriptions.push(this.renderer.on("drag",(e=>{this.options.interact&&(this.renderer.renderProgress(e),clearTimeout(t),t=setTimeout((()=>{this.seekTo(e)}),this.isPlaying()?0:200),this.emit("interaction"),this.emit("drag",e))})))}}initTimerEvents(){this.subscriptions.push(this.timer.on("tick",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),!0),this.emit("timeupdate",t),this.emit("audioprocess",t)})))}initPlugins(){this.options.plugins?.length&&this.options.plugins.forEach((t=>{this.registerPlugin(t)}))}registerPlugin(t){return t.init(this),this.plugins.push(t),t}getWrapper(){return this.renderer.getWrapper()}getScroll(){return this.renderer.getScroll()}getActivePlugins(){return this.plugins}async load(t,e,r){if(this.decodedData=null,this.emit("load",t),e)this.setSrc(t),r||(r=await new Promise((t=>{this.onceMediaEvent("loadedmetadata",(()=>t(this.getMediaElement().duration)))}))||0),this.decodedData=i.createBuffer(e,r);else{const e=await s(t);this.setSrc(t,e),this.decodedData=await i.decode(e,this.options.sampleRate)}this.emit("decode",this.getDuration()),this.emit("ready",this.getDuration()),this.renderer.render(this.decodedData)}zoom(t){if(!this.decodedData)throw new Error("No audio loaded");this.renderer.zoom(t),this.emit("zoom",t)}getDecodedData(){return this.decodedData}getDuration(){const t=super.getDuration();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.subscriptions.forEach((t=>t())),this.plugins.forEach((t=>t.destroy())),this.timer.destroy(),this.renderer.destroy(),super.destroy()}}const d=c;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={decode:async function(t,e){const i=new AudioContext({sampleRate:e}),s=i.decodeAudioData(t);return s.finally((()=>i.close())),s},createBuffer:function(t,e){return"number"==typeof t[0]&&(t=[t]),function(t){const e=t[0];if(e.some((t=>t>1||t<-1))){const i=e.length;let s=0;for(let t=0;t<i;t++){const i=Math.abs(e[t]);i>s&&(s=i)}for(const e of t)for(let t=0;t<i;t++)e[t]/=s}}(t),{duration:e,length:t[0].length,sampleRate:t[0].length/e,numberOfChannels:t.length,getChannelData:e=>t?.[e],copyFromChannel:AudioBuffer.prototype.copyFromChannel,copyToChannel:AudioBuffer.prototype.copyToChannel}}},s=async function(t){return fetch(t).then((t=>t.arrayBuffer()))},n=class{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const i=this.on(t,e),s=this.on(t,(()=>{i(),s()}));return i}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}},r=class extends n{constructor(t){super(),this.isExternalMedia=!1,t.media?(this.media=t.media,this.isExternalMedia=!0):this.media=document.createElement("audio"),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})}revokeSrc(){(this.media.currentSrc||this.media.src||"").startsWith("blob:")&&URL.revokeObjectURL(this.media.currentSrc)}setSrc(t,e){if((this.media.currentSrc||this.media.src||"")===t)return;this.revokeSrc();const i=e?URL.createObjectURL(new Blob([e],{type:"audio/wav"})):t;this.media.src=i}destroy(){this.media.pause(),this.revokeSrc(),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.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)}};class o extends n{constructor(t){let e;if(super(),this.timeouts=[],this.isScrolling=!1,this.audioData=null,this.resizeObserver=null,this.isDragging=!1,this.options=t,"string"==typeof t.container?e=document.querySelector(t.container):t.container instanceof HTMLElement&&(e=t.container),!e)throw new Error("Container not found");const[i,s]=this.initHtml();e.appendChild(i),this.container=i,this.scrollContainer=s.querySelector(".scroll"),this.wrapper=s.querySelector(".wrapper"),this.canvasWrapper=s.querySelector(".canvases"),this.progressWrapper=s.querySelector(".progress"),this.cursor=s.querySelector(".cursor"),this.initEvents()}initEvents(){this.wrapper.addEventListener("click",(t=>{const e=this.wrapper.getBoundingClientRect(),i=(t.clientX-e.left)/e.width;this.emit("click",i)})),this.initDrag(),this.scrollContainer.addEventListener("scroll",(()=>{const{scrollLeft:t,scrollWidth:e,clientWidth:i}=this.scrollContainer,s=t/e,n=(t+i)/e;this.emit("scroll",s,n)}));const t=this.createDelay(100);this.resizeObserver=new ResizeObserver((()=>{t((()=>this.reRender()))})),this.resizeObserver.observe(this.scrollContainer)}initDrag(){!function(t,e,i,s,n=5){let r=()=>{};if(!t)return r;t.addEventListener("pointerdown",(o=>{o.preventDefault(),o.stopPropagation();let a=o.clientX,h=o.clientY,l=!1;const c=s=>{s.preventDefault(),s.stopPropagation();const r=s.clientX,o=s.clientY;if(l||Math.abs(r-a)>=n||Math.abs(o-h)>=n){l||(l=!0,i?.(a,h));const{left:s,top:n}=t.getBoundingClientRect();e(r-a,o-h,r-s,o-n),a=r,h=o}},d=t=>{l&&(t.preventDefault(),t.stopPropagation())};r=()=>{document.removeEventListener("pointermove",c),document.removeEventListener("pointerup",u),setTimeout((()=>{document.removeEventListener("click",d,!0)}),10)};const u=()=>{l&&s?.(),r()};document.addEventListener("pointermove",c),document.addEventListener("pointerup",u),document.addEventListener("click",d,!0)}))}(this.wrapper,((t,e,i)=>{this.emit("drag",Math.max(0,Math.min(1,i/this.wrapper.clientWidth)))}),(()=>this.isDragging=!0),(()=>this.isDragging=!1))}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 min-height: ${this.options.height}px;\n }\n :host .canvases > div {\n position: relative;\n }\n :host canvas {\n display: block;\n position: absolute;\n top: 0;\n image-rendering: pixelated;\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 }\n :host .progress > div {\n position: relative;\n }\n :host .cursor {\n pointer-events: none;\n position: absolute;\n z-index: 5;\n top: 0;\n left: 0;\n height: 100%;\n border-radius: 2px;\n }\n </style>\n\n <div class="scroll" part="scroll">\n <div class="wrapper">\n <div class="canvases"></div>\n <div class="progress"></div>\n <div class="cursor" part="cursor"></div>\n </div>\n </div>\n `,[t,e]}setOptions(t){this.options=t,this.reRender()}getWrapper(){return this.wrapper}getScroll(){return this.scrollContainer.scrollLeft}destroy(){this.container.remove(),this.resizeObserver?.disconnect()}createDelay(t=10){const e={};return this.timeouts.push(e),i=>{e.timeout&&clearTimeout(e.timeout),e.timeout=setTimeout(i,t)}}convertColorValues(t){if(!Array.isArray(t))return t||"";if(t.length<2)return 1===t.length?t[0]:"";const e=document.createElement("canvas"),i=e.getContext("2d").createLinearGradient(0,0,0,e.height),s=1/(t.length-1);return t.forEach(((t,e)=>{const n=e*s;i.addColorStop(n,t)})),i}renderBars(t,e,i){if(i.fillStyle=this.convertColorValues(e.waveColor),e.renderFunction)return void e.renderFunction(t,i);const s=t[0],n=t[1]||t[0],r=s.length,o=window.devicePixelRatio||1,{width:a,height:h}=i.canvas,l=h/2,c=e.barHeight||1,d=e.barWidth?e.barWidth*o:1,u=e.barGap?e.barGap*o:e.barWidth?d/2:0,p=e.barRadius||0,m=a/(d+u)/r;let g=1;if(e.normalize){g=0;for(let t=0;t<r;t++){const e=Math.abs(s[t]);e>g&&(g=e)}}const v=l/g*c;i.beginPath();let f=0,y=0,b=0;for(let t=0;t<=r;t++){const e=Math.round(t*m);if(e>f){const t=Math.round(y*v),s=Math.round(b*v);i.roundRect(f*(d+u),l-t,d,t+s||1,p),f=e,y=0,b=0}const r=Math.abs(s[t]||0),o=Math.abs(n[t]||0);r>y&&(y=r),o>b&&(b=o)}i.fill(),i.closePath()}renderSingleCanvas(t,e,i,s,n,r,o){const a=window.devicePixelRatio||1,h=e.height||0,l=document.createElement("canvas"),c=t[0].length;l.width=Math.round(i*(n-s)/c),l.height=h,l.style.width=`${Math.floor(l.width/a)}px`,l.style.height=`${e.height}px`,l.style.left=`${Math.floor(s*i/a/c)}px`,r.appendChild(l);const d=l.getContext("2d",{desynchronized:!0});this.renderBars(t.map((t=>t.slice(s,n))),e,d);const u=l.cloneNode();o.appendChild(u);const p=u.getContext("2d",{desynchronized:!0});l.width>0&&l.height>0&&p.drawImage(l,0,0),p.globalCompositeOperation="source-in",p.fillStyle=this.convertColorValues(e.progressColor),p.fillRect(0,0,l.width,l.height)}renderWaveform(t,e,i){const s=document.createElement("div");s.style.height=`${e.height}px`,this.canvasWrapper.appendChild(s);const n=s.cloneNode();this.progressWrapper.appendChild(n);const{scrollLeft:r,scrollWidth:a,clientWidth:h}=this.scrollContainer,l=t[0].length,c=l/a,d=Math.min(o.MAX_CANVAS_WIDTH,h),u=Math.floor(Math.abs(r)*c),p=Math.ceil(u+d*c),m=p-u,g=(r,o)=>{this.renderSingleCanvas(t,e,i,Math.max(0,r),Math.min(o,l),s,n)},v=this.createDelay(),f=this.createDelay(),y=(t,e)=>{g(t,e),t>0&&v((()=>{y(t-m,e-m)}))},b=(t,e)=>{g(t,e),e<l&&f((()=>{b(t+m,e+m)}))};y(u,p),p<l&&b(p,p+m)}render(t){this.timeouts.forEach((t=>t.timeout&&clearTimeout(t.timeout))),this.timeouts=[];const e=window.devicePixelRatio||1,i=this.scrollContainer.clientWidth,s=Math.ceil(t.duration*(this.options.minPxPerSec||0));this.isScrolling=s>i;const n=this.options.fillParent&&!this.isScrolling,r=(n?i:s)*e;if(this.wrapper.style.width=n?"100%":`${s}px`,this.scrollContainer.style.overflowX=this.isScrolling?"auto":"hidden",this.scrollContainer.classList.toggle("noScrollbar",!!this.options.hideScrollbar),this.cursor.style.backgroundColor=`${this.options.cursorColor||this.options.progressColor}`,this.cursor.style.width=`${this.options.cursorWidth}px`,this.canvasWrapper.innerHTML="",this.progressWrapper.innerHTML="",this.options.splitChannels)for(let e=0;e<t.numberOfChannels;e++){const i={...this.options,...this.options.splitChannels[e]};this.renderWaveform([t.getChannelData(e)],i,r)}else{const e=[t.getChannelData(0)];t.numberOfChannels>1&&e.push(t.getChannelData(1)),this.renderWaveform(e,this.options,r)}this.audioData=t,this.emit("render")}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()}scrollIntoView(t,e=!1){const{clientWidth:i,scrollLeft:s,scrollWidth:n}=this.scrollContainer,r=n*t,o=i/2;if(r>s+(e&&this.options.autoCenter&&!this.isDragging?o:i)||r<s)if(this.options.autoCenter&&!this.isDragging){const t=o/20;r-(s+o)>=t&&r<s+i?this.scrollContainer.scrollLeft+=t:this.scrollContainer.scrollLeft=r-o}else if(this.isDragging){const t=10;this.scrollContainer.scrollLeft=r<s?r-t:r-i+t}else this.scrollContainer.scrollLeft=r;{const{scrollLeft:t}=this.scrollContainer,e=t/n,s=(t+i)/n;this.emit("scroll",e,s)}}renderProgress(t,e){isNaN(t)||(this.progressWrapper.style.width=100*t+"%",this.cursor.style.left=100*t+"%",this.cursor.style.marginLeft=100===Math.round(100*t)?`-${this.options.cursorWidth}px`:"",this.isScrolling&&this.options.autoScroll&&this.scrollIntoView(t,e))}}o.MAX_CANVAS_WIDTH=4e3;const a=o,h=class extends n{constructor(){super(...arguments),this.unsubscribe=()=>{}}start(){this.unsubscribe=this.on("tick",(()=>{requestAnimationFrame((()=>{this.emit("tick")}))})),this.emit("tick")}stop(){this.unsubscribe()}destroy(){this.unsubscribe()}},l={height:128,waveColor:"#999",progressColor:"#555",cursorWidth:1,minPxPerSec:0,fillParent:!0,interact:!0,autoScroll:!0,autoCenter:!0,sampleRate:8e3};class c extends r{static create(t){return new c(t)}constructor(t){super({media:t.media,autoplay:t.autoplay,playbackRate:t.audioRate}),this.plugins=[],this.decodedData=null,this.subscriptions=[],this.options=Object.assign({},l,t),this.timer=new h,this.renderer=new a(this.options),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents(),this.initPlugins();const e=this.options.url||this.options.media?.currentSrc||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),t.audioRate&&this.setPlaybackRate(t.audioRate)}initPlayerEvents(){this.subscriptions.push(this.onMediaEvent("timeupdate",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),this.isPlaying()),this.emit("timeupdate",t)})),this.onMediaEvent("play",(()=>{this.emit("play"),this.timer.start()})),this.onMediaEvent("pause",(()=>{this.emit("pause"),this.timer.stop()})),this.onMediaEvent("ended",(()=>{this.emit("finish")})),this.onMediaEvent("seeking",(()=>{this.emit("seeking",this.getCurrentTime())})))}initRendererEvents(){this.subscriptions.push(this.renderer.on("click",(t=>{this.options.interact&&(this.seekTo(t),this.emit("interaction"),this.emit("click",t))})),this.renderer.on("scroll",((t,e)=>{const i=this.getDuration();this.emit("scroll",t*i,e*i)})),this.renderer.on("render",(()=>{this.emit("redraw")})));{let t;this.subscriptions.push(this.renderer.on("drag",(e=>{this.options.interact&&(this.renderer.renderProgress(e),clearTimeout(t),t=setTimeout((()=>{this.seekTo(e)}),this.isPlaying()?0:200),this.emit("interaction"),this.emit("drag",e))})))}}initTimerEvents(){this.subscriptions.push(this.timer.on("tick",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),!0),this.emit("timeupdate",t),this.emit("audioprocess",t)})))}initPlugins(){this.options.plugins?.length&&this.options.plugins.forEach((t=>{this.registerPlugin(t)}))}registerPlugin(t){return t.init(this),this.plugins.push(t),t}getWrapper(){return this.renderer.getWrapper()}getScroll(){return this.renderer.getScroll()}getActivePlugins(){return this.plugins}async load(t,e,n){if(this.decodedData=null,this.emit("load",t),e)this.setSrc(t),n||(n=await new Promise((t=>{this.onceMediaEvent("loadedmetadata",(()=>t(this.getMediaElement().duration)))}))||0),this.decodedData=i.createBuffer(e,n);else{const e=await s(t);this.setSrc(t,e),this.decodedData=await i.decode(e,this.options.sampleRate)}this.emit("decode",this.getDuration()),this.emit("ready",this.getDuration()),this.renderer.render(this.decodedData)}zoom(t){if(!this.decodedData)throw new Error("No audio loaded");this.renderer.zoom(t),this.emit("zoom",t)}getDecodedData(){return this.decodedData}getDuration(){const t=super.getDuration();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)}async playPause(){return this.isPlaying()?this.pause():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.subscriptions.forEach((t=>t())),this.plugins.forEach((t=>t.destroy())),this.timer.destroy(),this.renderer.destroy(),super.destroy()}}const d=c;return e.default})()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wavesurfer.js",
3
- "version": "7.0.0-beta.5",
3
+ "version": "7.0.0-beta.6",
4
4
  "license": "BSD-3-Clause",
5
5
  "author": "katspaugh",
6
6
  "description": "Navigable audio waveform player",
@@ -28,6 +28,9 @@
28
28
  "import": "./dist/wavesurfer.js",
29
29
  "require": "./dist/wavesurfer.min.js"
30
30
  },
31
+ "./dist/*": {
32
+ "import": "./dist/*"
33
+ },
31
34
  "./dist/plugins/*": {
32
35
  "import": "./dist/plugins/*.js",
33
36
  "require": "./dist/plugins/*.min.js"