wavesurfer.js 7.0.0-alpha.9 → 7.0.0-beta.10

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 (55) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +137 -20
  3. package/dist/base-plugin.d.ts +6 -4
  4. package/dist/base-plugin.js +9 -3
  5. package/dist/decoder.d.ts +8 -7
  6. package/dist/decoder.js +43 -38
  7. package/dist/draggable.d.ts +1 -0
  8. package/dist/draggable.js +59 -0
  9. package/dist/event-emitter.d.ts +16 -10
  10. package/dist/event-emitter.js +38 -16
  11. package/dist/fetcher.d.ts +6 -3
  12. package/dist/fetcher.js +9 -15
  13. package/dist/player.d.ts +31 -11
  14. package/dist/player.js +58 -31
  15. package/dist/plugins/envelope.d.ts +71 -0
  16. package/dist/plugins/envelope.js +326 -0
  17. package/dist/plugins/envelope.min.cjs +1 -0
  18. package/dist/plugins/minimap.d.ts +39 -0
  19. package/dist/plugins/minimap.js +114 -0
  20. package/dist/plugins/minimap.min.cjs +1 -0
  21. package/dist/plugins/record.d.ts +28 -0
  22. package/dist/plugins/record.js +132 -0
  23. package/dist/plugins/record.min.cjs +1 -0
  24. package/dist/plugins/regions.d.ts +84 -43
  25. package/dist/plugins/regions.js +330 -195
  26. package/dist/plugins/regions.min.cjs +1 -0
  27. package/dist/plugins/spectrogram-fft.d.ts +9 -0
  28. package/dist/plugins/spectrogram-fft.js +150 -0
  29. package/dist/plugins/spectrogram.d.ts +72 -0
  30. package/dist/plugins/spectrogram.js +341 -0
  31. package/dist/plugins/spectrogram.min.cjs +1 -0
  32. package/dist/plugins/timeline.d.ts +25 -9
  33. package/dist/plugins/timeline.js +65 -24
  34. package/dist/plugins/timeline.min.cjs +1 -0
  35. package/dist/renderer.d.ts +32 -26
  36. package/dist/renderer.js +363 -167
  37. package/dist/timer.d.ts +1 -1
  38. package/dist/wavesurfer.d.ts +154 -0
  39. package/dist/wavesurfer.js +230 -0
  40. package/dist/wavesurfer.min.cjs +1 -0
  41. package/package.json +61 -28
  42. package/dist/index.d.ts +0 -117
  43. package/dist/index.js +0 -227
  44. package/dist/player-webaudio.d.ts +0 -8
  45. package/dist/player-webaudio.js +0 -32
  46. package/dist/plugins/multitrack.d.ts +0 -44
  47. package/dist/plugins/multitrack.js +0 -260
  48. package/dist/plugins/xmultitrack.d.ts +0 -44
  49. package/dist/plugins/xmultitrack.js +0 -260
  50. package/dist/react/useWavesurfer.d.ts +0 -5
  51. package/dist/react/useWavesurfer.js +0 -20
  52. package/dist/wavesurfer.Multitrack.min.js +0 -1
  53. package/dist/wavesurfer.Regions.min.js +0 -1
  54. package/dist/wavesurfer.Timeline.min.js +0 -1
  55. package/dist/wavesurfer.min.js +0 -1
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Minimap is a tiny copy of the main waveform serving as a navigation tool.
3
+ */
4
+ import BasePlugin from '../base-plugin.js';
5
+ import WaveSurfer from '../wavesurfer.js';
6
+ const defaultOptions = {
7
+ height: 50,
8
+ overlayColor: 'rgba(100, 100, 100, 0.1)',
9
+ insertPosition: 'afterend',
10
+ };
11
+ class MinimapPlugin extends BasePlugin {
12
+ constructor(options) {
13
+ super(options);
14
+ this.miniWavesurfer = null;
15
+ this.container = null;
16
+ this.options = Object.assign({}, defaultOptions, options);
17
+ this.minimapWrapper = this.initMinimapWrapper();
18
+ this.overlay = this.initOverlay();
19
+ }
20
+ static create(options) {
21
+ return new MinimapPlugin(options);
22
+ }
23
+ /** Called by wavesurfer, don't call manually */
24
+ onInit() {
25
+ if (!this.wavesurfer) {
26
+ throw Error('WaveSurfer is not initialized');
27
+ }
28
+ if (this.options.container) {
29
+ if (typeof this.options.container === 'string') {
30
+ this.container = document.querySelector(this.options.container);
31
+ }
32
+ else if (this.options.container instanceof HTMLElement) {
33
+ this.container = this.options.container;
34
+ }
35
+ this.container?.appendChild(this.minimapWrapper);
36
+ }
37
+ else {
38
+ this.container = this.wavesurfer.getWrapper().parentElement;
39
+ this.container?.insertAdjacentElement(this.options.insertPosition, this.minimapWrapper);
40
+ }
41
+ this.initWaveSurferEvents();
42
+ }
43
+ initMinimapWrapper() {
44
+ const div = document.createElement('div');
45
+ div.style.position = 'relative';
46
+ div.setAttribute('part', 'minimap');
47
+ return div;
48
+ }
49
+ initOverlay() {
50
+ const div = document.createElement('div');
51
+ div.setAttribute('style', 'position: absolute; z-index: 2; left: 0; top: 0; bottom: 0; transition: left 100ms ease-out; pointer-events: none;');
52
+ div.style.backgroundColor = this.options.overlayColor;
53
+ this.minimapWrapper.appendChild(div);
54
+ return div;
55
+ }
56
+ initMinimap() {
57
+ if (this.miniWavesurfer) {
58
+ this.miniWavesurfer.destroy();
59
+ this.miniWavesurfer = null;
60
+ }
61
+ if (!this.wavesurfer)
62
+ return;
63
+ const data = this.wavesurfer.getDecodedData();
64
+ const media = this.wavesurfer.getMediaElement();
65
+ if (!data || !media)
66
+ return;
67
+ this.miniWavesurfer = WaveSurfer.create({
68
+ ...this.options,
69
+ container: this.minimapWrapper,
70
+ minPxPerSec: 0,
71
+ fillParent: true,
72
+ media,
73
+ peaks: [data.getChannelData(0)],
74
+ duration: data.duration,
75
+ });
76
+ this.subscriptions.push(this.miniWavesurfer.on('ready', () => {
77
+ this.emit('ready');
78
+ }), this.miniWavesurfer.on('interaction', () => {
79
+ this.emit('interaction');
80
+ }));
81
+ }
82
+ getOverlayWidth() {
83
+ const waveformWidth = this.wavesurfer?.getWrapper().clientWidth || 1;
84
+ return Math.round((this.minimapWrapper.clientWidth / waveformWidth) * 100);
85
+ }
86
+ onRedraw() {
87
+ const overlayWidth = this.getOverlayWidth();
88
+ this.overlay.style.width = `${overlayWidth}%`;
89
+ }
90
+ onScroll(startTime) {
91
+ if (!this.wavesurfer)
92
+ return;
93
+ const duration = this.wavesurfer.getDuration();
94
+ this.overlay.style.left = `${(startTime / duration) * 100}%`;
95
+ }
96
+ initWaveSurferEvents() {
97
+ if (!this.wavesurfer)
98
+ return;
99
+ this.subscriptions.push(this.wavesurfer.on('decode', () => {
100
+ this.initMinimap();
101
+ }), this.wavesurfer.on('scroll', (startTime) => {
102
+ this.onScroll(startTime);
103
+ }), this.wavesurfer.on('redraw', () => {
104
+ this.onRedraw();
105
+ }));
106
+ }
107
+ /** Unmount */
108
+ destroy() {
109
+ this.miniWavesurfer?.destroy();
110
+ this.minimapWrapper.remove();
111
+ super.destroy();
112
+ }
113
+ }
114
+ export default MinimapPlugin;
@@ -0,0 +1 @@
1
+ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Minimap=e():t.Minimap=e()}(WaveSurfer,(()=>(()=>{"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:()=>g});const i=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)))}},s=class extends i{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.subscriptions.forEach((t=>t()))}},r={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}}},n=async function(t){return fetch(t).then((t=>t.blob()))},o=class extends i{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 instanceof Blob?URL.createObjectURL(e):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 a extends i{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");this.parent=e;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(){!function(t,e,i,s,r=5){let n=()=>{};if(!t)return n;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 n=s.clientX,o=s.clientY;if(l||Math.abs(n-a)>=r||Math.abs(o-h)>=r){l||(l=!0,i?.(a,h));const{left:s,top:r}=t.getBoundingClientRect();e(n-a,o-h,n-s,o-r),a=n,h=o}},d=t=>{l&&(t.preventDefault(),t.stopPropagation())},u=()=>{l&&s?.(),n()};n=()=>{document.removeEventListener("pointermove",c),document.removeEventListener("pointerup",u),document.removeEventListener("pointerleave",u),setTimeout((()=>{document.removeEventListener("click",d,!0)}),10)},document.addEventListener("pointermove",c),document.addEventListener("pointerup",u),document.addEventListener("pointerleave",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))}getHeight(){return null==this.options.height?128:isNaN(Number(this.options.height))?"auto"===this.options.height&&this.parent.clientHeight||128:Number(this.options.height)}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.getHeight()}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" part="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 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}renderBars(t,e,i){if(i.fillStyle=this.convertColorValues(e.waveColor),e.renderFunction)return void e.renderFunction(t,i);const s=t[0],r=t[1]||t[0],n=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)/n;let g=1;if(e.normalize){g=0;for(let t=0;t<n;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<=n;t++){const n=Math.round(t*m);if(n>f){const t=Math.round(y*v),s=t+Math.round(b*v)||1;let r=l-t;"top"===e.barAlign?r=0:"bottom"===e.barAlign&&(r=h-s),i.roundRect(f*(d+u),r,d,s,p),f=n,y=0,b=0}const o=Math.abs(s[t]||0),a=Math.abs(r[t]||0);o>y&&(y=o),a>b&&(b=a)}i.fill(),i.closePath()}renderSingleCanvas(t,e,i,s,r,n,o,a){const h=window.devicePixelRatio||1,l=document.createElement("canvas"),c=t[0].length;l.width=Math.round(i*(n-r)/c),l.height=s*h,l.style.width=`${Math.floor(l.width/h)}px`,l.style.height=`${s}px`,l.style.left=`${Math.floor(r*i/h/c)}px`,o.appendChild(l);const d=l.getContext("2d");this.renderBars(t.map((t=>t.slice(r,n))),e,d);const u=l.cloneNode();a.appendChild(u);const p=u.getContext("2d");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"),r=this.getHeight();s.style.height=`${r}px`,this.canvasWrapper.style.minHeight=`${r}px`,this.canvasWrapper.appendChild(s);const n=s.cloneNode();this.progressWrapper.appendChild(n);const{scrollLeft:o,scrollWidth:h,clientWidth:l}=this.scrollContainer,c=t[0].length,d=c/h;let u=Math.min(a.MAX_CANVAS_WIDTH,l);if(e.barWidth||e.barGap){const t=e.barWidth||.5,i=t+(e.barGap||t/2);u%i!=0&&(u=Math.floor(u/i)*i)}const p=Math.floor(Math.abs(o)*d),m=Math.floor(p+u*d),g=m-p,v=(o,a)=>{this.renderSingleCanvas(t,e,i,r,Math.max(0,o),Math.min(a,c),s,n)},f=this.createDelay(),y=this.createDelay(),b=(t,e)=>{v(t,e),t>0&&f((()=>{b(t-g,e-g)}))},w=(t,e)=>{v(t,e),e<c&&y((()=>{w(t+g,e+g)}))};b(p,m),m<c&&w(m,m+g)}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))}}a.MAX_CANVAS_WIDTH=4e3;const h=a,l=class extends i{constructor(){super(...arguments),this.unsubscribe=()=>{}}start(){this.unsubscribe=this.on("tick",(()=>{requestAnimationFrame((()=>{this.emit("tick")}))})),this.emit("tick")}stop(){this.unsubscribe()}destroy(){this.unsubscribe()}},c={waveColor:"#999",progressColor:"#555",cursorWidth:1,minPxPerSec:0,fillParent:!0,interact:!0,autoScroll:!0,autoCenter:!0,sampleRate:8e3};class d extends o{static create(t){return new d(t)}constructor(t){super({media:t.media,autoplay:t.autoplay,playbackRate:t.audioRate}),this.plugins=[],this.decodedData=null,this.duration=null,this.subscriptions=[],this.options=Object.assign({},c,t),this.timer=new l,this.renderer=new h(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=Object.assign({},this.options,t),this.renderer.setOptions(this.options),t.audioRate&&this.setPlaybackRate(t.audioRate)}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)})))}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.getCurrentTime()),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",e*this.getDuration()),this.emit("drag",e))})))}}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,i){this.decodedData=null,this.duration=null,this.emit("load",t);const s=e?void 0:await n(t);if(this.setSrc(t,s),this.duration=i||this.getDuration()||await new Promise((t=>{this.onceMediaEvent("loadedmetadata",(()=>t(this.getDuration())))}))||0,e)this.decodedData=r.createBuffer(e,this.duration);else if(s){const t=await s.arrayBuffer();this.decodedData=await r.decode(t,this.options.sampleRate),0!==this.duration&&this.duration!==1/0||(this.duration=this.decodedData.duration)}this.emit("decode",this.duration),this.decodedData&&this.renderer.render(this.decodedData),this.emit("ready",this.duration)}zoom(t){if(!this.decodedData)throw new Error("No audio loaded");this.renderer.zoom(t),this.emit("zoom",t)}getDecodedData(){return this.decodedData}getDuration(){return null!==this.duration?this.duration:super.getDuration()}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 u=d,p={height:50,overlayColor:"rgba(100, 100, 100, 0.1)",insertPosition:"afterend"};class m extends s{constructor(t){super(t),this.miniWavesurfer=null,this.container=null,this.options=Object.assign({},p,t),this.minimapWrapper=this.initMinimapWrapper(),this.overlay=this.initOverlay()}static create(t){return new m(t)}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");this.options.container?("string"==typeof this.options.container?this.container=document.querySelector(this.options.container):this.options.container instanceof HTMLElement&&(this.container=this.options.container),this.container?.appendChild(this.minimapWrapper)):(this.container=this.wavesurfer.getWrapper().parentElement,this.container?.insertAdjacentElement(this.options.insertPosition,this.minimapWrapper)),this.initWaveSurferEvents()}initMinimapWrapper(){const t=document.createElement("div");return t.style.position="relative",t.setAttribute("part","minimap"),t}initOverlay(){const t=document.createElement("div");return t.setAttribute("style","position: absolute; z-index: 2; left: 0; top: 0; bottom: 0; transition: left 100ms ease-out; pointer-events: none;"),t.style.backgroundColor=this.options.overlayColor,this.minimapWrapper.appendChild(t),t}initMinimap(){if(this.miniWavesurfer&&(this.miniWavesurfer.destroy(),this.miniWavesurfer=null),!this.wavesurfer)return;const t=this.wavesurfer.getDecodedData(),e=this.wavesurfer.getMediaElement();t&&e&&(this.miniWavesurfer=u.create({...this.options,container:this.minimapWrapper,minPxPerSec:0,fillParent:!0,media:e,peaks:[t.getChannelData(0)],duration:t.duration}),this.subscriptions.push(this.miniWavesurfer.on("ready",(()=>{this.emit("ready")})),this.miniWavesurfer.on("interaction",(()=>{this.emit("interaction")}))))}getOverlayWidth(){const t=this.wavesurfer?.getWrapper().clientWidth||1;return Math.round(this.minimapWrapper.clientWidth/t*100)}onRedraw(){const t=this.getOverlayWidth();this.overlay.style.width=`${t}%`}onScroll(t){if(!this.wavesurfer)return;const e=this.wavesurfer.getDuration();this.overlay.style.left=t/e*100+"%"}initWaveSurferEvents(){this.wavesurfer&&this.subscriptions.push(this.wavesurfer.on("decode",(()=>{this.initMinimap()})),this.wavesurfer.on("scroll",(t=>{this.onScroll(t)})),this.wavesurfer.on("redraw",(()=>{this.onRedraw()})))}destroy(){this.miniWavesurfer?.destroy(),this.minimapWrapper.remove(),super.destroy()}}const g=m;return e.default})()));
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Record audio from the microphone, render a waveform and download the audio.
3
+ */
4
+ import BasePlugin from '../base-plugin.js';
5
+ export type RecordPluginOptions = {
6
+ realtimeWaveColor?: string;
7
+ lineWidth?: number;
8
+ mimeType?: MediaRecorderOptions['mimeType'];
9
+ audioBitsPerSecond?: MediaRecorderOptions['audioBitsPerSecond'];
10
+ };
11
+ export type RecordPluginEvents = {
12
+ startRecording: [];
13
+ stopRecording: [];
14
+ };
15
+ declare class RecordPlugin extends BasePlugin<RecordPluginEvents, RecordPluginOptions> {
16
+ private mediaRecorder;
17
+ private recordedUrl;
18
+ static create(options?: RecordPluginOptions): RecordPlugin;
19
+ private loadBlob;
20
+ render(stream: MediaStream): () => void;
21
+ private cleanUp;
22
+ startRecording(): Promise<void>;
23
+ isRecording(): boolean;
24
+ stopRecording(): void;
25
+ getRecordedUrl(): string;
26
+ destroy(): void;
27
+ }
28
+ export default RecordPlugin;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Record audio from the microphone, render a waveform and download the audio.
3
+ */
4
+ import BasePlugin from '../base-plugin.js';
5
+ const MIME_TYPES = ['audio/webm', 'audio/wav', 'audio/mpeg', 'audio/mp4', 'audio/mp3'];
6
+ const findSupportedMimeType = () => MIME_TYPES.find((mimeType) => MediaRecorder.isTypeSupported(mimeType));
7
+ class RecordPlugin extends BasePlugin {
8
+ constructor() {
9
+ super(...arguments);
10
+ this.mediaRecorder = null;
11
+ this.recordedUrl = '';
12
+ }
13
+ static create(options) {
14
+ return new RecordPlugin(options || {});
15
+ }
16
+ loadBlob(data, type) {
17
+ const blob = new Blob(data, { type });
18
+ this.recordedUrl = URL.createObjectURL(blob);
19
+ this.wavesurfer?.load(this.recordedUrl);
20
+ }
21
+ render(stream) {
22
+ if (!this.wavesurfer)
23
+ return () => undefined;
24
+ const container = this.wavesurfer.getWrapper();
25
+ const canvas = document.createElement('canvas');
26
+ canvas.width = container.clientWidth;
27
+ canvas.height = container.clientHeight;
28
+ canvas.style.zIndex = '10';
29
+ container.appendChild(canvas);
30
+ const canvasCtx = canvas.getContext('2d');
31
+ const audioContext = new AudioContext();
32
+ const source = audioContext.createMediaStreamSource(stream);
33
+ const analyser = audioContext.createAnalyser();
34
+ source.connect(analyser);
35
+ let animationId;
36
+ const drawWaveform = () => {
37
+ if (!canvasCtx)
38
+ return;
39
+ canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
40
+ const bufferLength = analyser.frequencyBinCount;
41
+ const dataArray = new Uint8Array(bufferLength);
42
+ analyser.getByteTimeDomainData(dataArray);
43
+ canvasCtx.lineWidth = this.options.lineWidth || 2;
44
+ const color = this.options.realtimeWaveColor || this.wavesurfer?.options.waveColor || '';
45
+ canvasCtx.strokeStyle = Array.isArray(color) ? color[0] : color;
46
+ canvasCtx.beginPath();
47
+ const sliceWidth = (canvas.width * 1.0) / bufferLength;
48
+ let x = 0;
49
+ for (let i = 0; i < bufferLength; i++) {
50
+ const v = dataArray[i] / 128.0;
51
+ const y = (v * canvas.height) / 2;
52
+ if (i === 0) {
53
+ canvasCtx.moveTo(x, y);
54
+ }
55
+ else {
56
+ canvasCtx.lineTo(x, y);
57
+ }
58
+ x += sliceWidth;
59
+ }
60
+ canvasCtx.lineTo(canvas.width, canvas.height / 2);
61
+ canvasCtx.stroke();
62
+ animationId = requestAnimationFrame(drawWaveform);
63
+ };
64
+ drawWaveform();
65
+ return () => {
66
+ if (animationId) {
67
+ cancelAnimationFrame(animationId);
68
+ }
69
+ if (source) {
70
+ source.disconnect();
71
+ source.mediaStream.getTracks().forEach((track) => track.stop());
72
+ }
73
+ if (audioContext) {
74
+ audioContext.close();
75
+ }
76
+ canvas?.remove();
77
+ };
78
+ }
79
+ cleanUp() {
80
+ this.stopRecording();
81
+ this.wavesurfer?.empty();
82
+ if (this.recordedUrl) {
83
+ URL.revokeObjectURL(this.recordedUrl);
84
+ this.recordedUrl = '';
85
+ }
86
+ }
87
+ async startRecording() {
88
+ this.cleanUp();
89
+ let stream;
90
+ try {
91
+ stream = await navigator.mediaDevices.getUserMedia({ audio: true });
92
+ }
93
+ catch (err) {
94
+ throw new Error('Error accessing the microphone: ' + err.message);
95
+ }
96
+ const onStop = this.render(stream);
97
+ const mediaRecorder = new MediaRecorder(stream, {
98
+ mimeType: this.options.mimeType || findSupportedMimeType(),
99
+ audioBitsPerSecond: this.options.audioBitsPerSecond,
100
+ });
101
+ const recordedChunks = [];
102
+ mediaRecorder.addEventListener('dataavailable', (event) => {
103
+ if (event.data.size > 0) {
104
+ recordedChunks.push(event.data);
105
+ }
106
+ });
107
+ mediaRecorder.addEventListener('stop', () => {
108
+ onStop();
109
+ this.loadBlob(recordedChunks, mediaRecorder.mimeType);
110
+ this.emit('stopRecording');
111
+ });
112
+ mediaRecorder.start();
113
+ this.emit('startRecording');
114
+ this.mediaRecorder = mediaRecorder;
115
+ }
116
+ isRecording() {
117
+ return this.mediaRecorder?.state === 'recording';
118
+ }
119
+ stopRecording() {
120
+ if (this.isRecording()) {
121
+ this.mediaRecorder?.stop();
122
+ }
123
+ }
124
+ getRecordedUrl() {
125
+ return this.recordedUrl;
126
+ }
127
+ destroy() {
128
+ super.destroy();
129
+ this.cleanUp();
130
+ }
131
+ }
132
+ export default RecordPlugin;
@@ -0,0 +1 @@
1
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Record=t():e.Record=t()}(WaveSurfer,(()=>(()=>{"use strict";var e={d:(t,r)=>{for(var i in r)e.o(r,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:r[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>n});const r=class{constructor(){this.listeners={}}on(e,t){return this.listeners[e]||(this.listeners[e]=new Set),this.listeners[e].add(t),()=>this.un(e,t)}once(e,t){const r=this.on(e,t),i=this.on(e,(()=>{r(),i()}));return r}un(e,t){this.listeners[e]&&(t?this.listeners[e].delete(t):delete this.listeners[e])}unAll(){this.listeners={}}emit(e,...t){this.listeners[e]&&this.listeners[e].forEach((e=>e(...t)))}},i=class extends r{constructor(e){super(),this.subscriptions=[],this.options=e}onInit(){}init(e){this.wavesurfer=e,this.onInit()}destroy(){this.subscriptions.forEach((e=>e()))}},s=["audio/webm","audio/wav","audio/mpeg","audio/mp4","audio/mp3"];class o extends i{constructor(){super(...arguments),this.mediaRecorder=null,this.recordedUrl=""}static create(e){return new o(e||{})}loadBlob(e,t){const r=new Blob(e,{type:t});this.recordedUrl=URL.createObjectURL(r),this.wavesurfer?.load(this.recordedUrl)}render(e){if(!this.wavesurfer)return()=>{};const t=this.wavesurfer.getWrapper(),r=document.createElement("canvas");r.width=t.clientWidth,r.height=t.clientHeight,r.style.zIndex="10",t.appendChild(r);const i=r.getContext("2d"),s=new AudioContext,o=s.createMediaStreamSource(e),n=s.createAnalyser();let a;o.connect(n);const d=()=>{if(!i)return;i.clearRect(0,0,r.width,r.height);const e=n.frequencyBinCount,t=new Uint8Array(e);n.getByteTimeDomainData(t),i.lineWidth=this.options.lineWidth||2;const s=this.options.realtimeWaveColor||this.wavesurfer?.options.waveColor||"";i.strokeStyle=Array.isArray(s)?s[0]:s,i.beginPath();const o=1*r.width/e;let c=0;for(let s=0;s<e;s++){const e=t[s]/128*r.height/2;0===s?i.moveTo(c,e):i.lineTo(c,e),c+=o}i.lineTo(r.width,r.height/2),i.stroke(),a=requestAnimationFrame(d)};return d(),()=>{a&&cancelAnimationFrame(a),o&&(o.disconnect(),o.mediaStream.getTracks().forEach((e=>e.stop()))),s&&s.close(),r?.remove()}}cleanUp(){this.stopRecording(),this.wavesurfer?.empty(),this.recordedUrl&&(URL.revokeObjectURL(this.recordedUrl),this.recordedUrl="")}async startRecording(){let e;this.cleanUp();try{e=await navigator.mediaDevices.getUserMedia({audio:!0})}catch(e){throw new Error("Error accessing the microphone: "+e.message)}const t=this.render(e),r=new MediaRecorder(e,{mimeType:this.options.mimeType||s.find((e=>MediaRecorder.isTypeSupported(e))),audioBitsPerSecond:this.options.audioBitsPerSecond}),i=[];r.addEventListener("dataavailable",(e=>{e.data.size>0&&i.push(e.data)})),r.addEventListener("stop",(()=>{t(),this.loadBlob(i,r.mimeType),this.emit("stopRecording")})),r.start(),this.emit("startRecording"),this.mediaRecorder=r}isRecording(){return"recording"===this.mediaRecorder?.state}stopRecording(){this.isRecording()&&this.mediaRecorder?.stop()}getRecordedUrl(){return this.recordedUrl}destroy(){super.destroy(),this.cleanUp()}}const n=o;return t.default})()));
@@ -1,53 +1,94 @@
1
+ /**
2
+ * Regions are visual overlays on the waveform that can be used to mark segments of audio.
3
+ * Regions can be clicked on, dragged and resized.
4
+ * You can set the color and content of each region, as well as their HTML content.
5
+ */
1
6
  import BasePlugin from '../base-plugin.js';
2
- import { WaveSurferPluginParams } from '../index.js';
3
- type RegionsPluginOptions = {
4
- dragSelection?: boolean;
5
- draggable?: boolean;
6
- resizable?: boolean;
7
+ import EventEmitter from '../event-emitter.js';
8
+ export type RegionsPluginOptions = undefined;
9
+ export type RegionsPluginEvents = {
10
+ 'region-created': [region: Region];
11
+ 'region-updated': [region: Region];
12
+ 'region-clicked': [region: Region, e: MouseEvent];
13
+ 'region-double-clicked': [region: Region, e: MouseEvent];
7
14
  };
8
- type Region = {
9
- startTime: number;
10
- endTime: number;
11
- title: string;
12
- start: number;
13
- end: number;
14
- element: HTMLElement;
15
+ export type RegionEvents = {
16
+ remove: [];
17
+ update: [];
18
+ 'update-end': [];
19
+ play: [];
20
+ click: [event: MouseEvent];
21
+ dblclick: [event: MouseEvent];
22
+ over: [event: MouseEvent];
23
+ leave: [event: MouseEvent];
15
24
  };
16
- type RegionsPluginEvents = {
17
- 'region-created': {
18
- region: Region;
19
- };
20
- 'region-updated': {
21
- region: Region;
22
- };
23
- 'region-clicked': {
24
- region: Region;
25
- };
25
+ export type RegionParams = {
26
+ id?: string;
27
+ start: number;
28
+ end?: number;
29
+ drag?: boolean;
30
+ resize?: boolean;
31
+ color?: string;
32
+ content?: string | HTMLElement;
26
33
  };
34
+ export declare class Region extends EventEmitter<RegionEvents> {
35
+ private totalDuration;
36
+ element: HTMLElement;
37
+ id: string;
38
+ start: number;
39
+ end: number;
40
+ drag: boolean;
41
+ resize: boolean;
42
+ color: string;
43
+ content?: HTMLElement;
44
+ constructor(params: RegionParams, totalDuration: number);
45
+ private initElement;
46
+ private renderPosition;
47
+ private initMouseEvents;
48
+ private onStartMoving;
49
+ private onEndMoving;
50
+ private onUpdate;
51
+ private onMove;
52
+ private onResize;
53
+ private onEndResizing;
54
+ _setTotalDuration(totalDuration: number): void;
55
+ /** Play the region from start to end */
56
+ play(): void;
57
+ /** Update the region's options */
58
+ setOptions(options: {
59
+ color?: string;
60
+ drag?: boolean;
61
+ resize?: boolean;
62
+ start?: number;
63
+ end?: number;
64
+ }): void;
65
+ /** Remove the region */
66
+ remove(): void;
67
+ }
27
68
  declare class RegionsPlugin extends BasePlugin<RegionsPluginEvents, RegionsPluginOptions> {
28
- private dragStart;
29
- private wrapper;
30
69
  private regions;
31
- private createdRegion;
32
- private modifiedRegion;
33
- private isResizingLeft;
34
- private isMoving;
70
+ private regionsContainer;
71
+ /** Create an instance of RegionsPlugin */
72
+ constructor(options?: RegionsPluginOptions);
35
73
  /** Create an instance of RegionsPlugin */
36
- constructor(params: WaveSurferPluginParams, options: RegionsPluginOptions);
37
- /** Unmount */
74
+ static create(options?: RegionsPluginOptions): RegionsPlugin;
75
+ /** Called by wavesurfer, don't call manually */
76
+ onInit(): void;
77
+ private initRegionsContainer;
78
+ /** Get all created regions */
79
+ getRegions(): Region[];
80
+ private avoidOverlapping;
81
+ private saveRegion;
82
+ /** Create a region with given parameters */
83
+ addRegion(options: RegionParams): Region;
84
+ /**
85
+ * Enable creation of regions by dragging on an empty space on the waveform.
86
+ * Returns a function to disable the drag selection.
87
+ */
88
+ enableDragSelection(options: Omit<RegionParams, 'start' | 'end'>): () => void;
89
+ /** Remove all regions */
90
+ clearRegions(): void;
91
+ /** Destroy the plugin and clean up */
38
92
  destroy(): void;
39
- private initWrapper;
40
- private handleMouseDown;
41
- private handleMouseMove;
42
- private handleMouseUp;
43
- private createRegionElement;
44
- private createRegion;
45
- private addRegion;
46
- private updateRegion;
47
- private moveRegion;
48
- /** Create a region at a given start and end time, with an optional title */
49
- add(startTime: number, endTime: number, title?: string, color?: string): Region;
50
- /** Set the background color of a region */
51
- setRegionColor(region: Region, color: string): void;
52
93
  }
53
94
  export default RegionsPlugin;