waveform-renderer
Version:
High-performance audio waveform visualization library for the web. Create customizable, interactive waveform renderers with TypeScript support and zero dependencies.
2 lines (1 loc) • 7.21 kB
JavaScript
var waveformRenderer=function(e){"use strict";var t=Object.defineProperty,__publicField=(e,s,i)=>((e,s,i)=>s in e?t(e,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[s]=i)(e,"symbol"!=typeof s?s+"":s,i);const s={amplitude:1,backgroundColor:"#CCCCCC",barWidth:2,borderColor:"#000000",borderRadius:0,borderWidth:0,color:"#000000",gap:1,minPixelRatio:1,position:"center",progress:0,progressLine:{color:"#FF0000",heightPercent:1,position:"center",style:"solid",width:2},smoothing:!0};class EventEmitter{constructor(){__publicField(this,"events",new Map)}off(e,t){const s=this.events.get(e);if(s){const i=s.indexOf(t);-1!==i&&s.splice(i,1),0===s.length&&this.events.delete(e)}}on(e,t){this.events.has(e)||this.events.set(e,[]);this.events.get(e).push(t)}once(e,t){const onceCallback=s=>{this.off(e,onceCallback),t(s)};this.on(e,onceCallback)}removeAllListeners(e){e?this.events.delete(e):this.events.clear()}emit(e,t){const s=this.events.get(e);s&&s.forEach((e=>e(t)))}hasListeners(e){const t=this.events.get(e);return!!t&&t.length>0}}function calculateBarDimensions(e,t,s,i){const r=e*t*s;switch(i){case"bottom":return{height:r,y:t-r};case"top":return{height:r,y:0};default:return{height:r,y:(t-r)/2}}}function normalizePeaks(e){const t=Math.max(...e.map(Math.abs),1);return e.map((e=>e/t))}function normalizeProgress(e){return Math.max(0,Math.min(1,e))}return e.WaveformRenderer=class WaveformRenderer extends EventEmitter{constructor(e,t,i={}){super(),__publicField(this,"canvas"),__publicField(this,"ctx"),__publicField(this,"devicePixelRatio"),__publicField(this,"frameRequest"),__publicField(this,"isDestroyed",!1),__publicField(this,"options"),__publicField(this,"peaks"),__publicField(this,"resizeObserver"),__publicField(this,"handleClick",(e=>{if(e.preventDefault(),!this.isDestroyed)try{const t=this.calculateProgressFromEvent(e);this.emit("seek",t)}catch(t){this.handleError(t)}})),__publicField(this,"handleError",(e=>{console.error(e),this.emit("error",e instanceof Error?e:new Error("An unknown error occurred"))})),__publicField(this,"handleResize",(()=>{if(!this.isDestroyed)try{const e=this.canvas.getBoundingClientRect();this.emit("resize",{height:e.height,width:e.width}),this.resizeCanvas(),this.scheduleRender()}catch(e){this.handleError(e)}})),__publicField(this,"handleTouch",(e=>{if(e.preventDefault(),!this.isDestroyed&&e.changedTouches[0])try{const t=this.calculateProgressFromTouch(e.changedTouches[0]);this.emit("seek",t)}catch(t){this.handleError(t)}}));try{if(!e)throw new Error("Canvas element is required");if(!Array.isArray(t)||0===t.length)throw new Error("Peaks array is required and must not be empty");this.canvas=e;const r=this.canvas.getContext("2d");if(!r)throw new Error("Could not get 2D context from canvas");this.ctx=r,this.peaks=normalizePeaks(t),this.options={...s,...i,progressLine:i.progressLine?{...s.progressLine,...i.progressLine}:null},this.devicePixelRatio=Math.max(window.devicePixelRatio||1,this.options.minPixelRatio),this.setupContext(),this.resizeObserver=new ResizeObserver(this.handleResize),this.resizeObserver.observe(this.canvas),this.canvas.addEventListener("click",this.handleClick),this.canvas.addEventListener("touchstart",this.handleTouch),this.resizeCanvas(),this.scheduleRender(),requestAnimationFrame((()=>this.emit("ready",void 0)))}catch(r){this.handleError(r)}}destroy(){this.isDestroyed||(this.emit("destroy",void 0),this.isDestroyed=!0,this.resizeObserver.disconnect(),this.canvas.removeEventListener("click",this.handleClick),this.canvas.removeEventListener("touchend",this.handleTouch),this.frameRequest&&cancelAnimationFrame(this.frameRequest))}setOptions(e){this.isDestroyed||(this.options={...this.options,...e},this.setupContext(),this.scheduleRender())}setPeaks(e){if(!this.isDestroyed)try{if(!Array.isArray(e)||0===e.length)throw new Error("Peaks array must not be empty");this.peaks=normalizePeaks(e),this.scheduleRender()}catch(t){this.handleError(t)}}setProgress(e){if(!this.isDestroyed)try{const t=normalizeProgress(e);this.options.progress=t,this.emit("progressChange",t),this.scheduleRender()}catch(t){this.handleError(t)}}setProgressLineOptions(e){if(!this.isDestroyed)try{this.options.progressLine=e?{...s.progressLine,...this.options.progressLine,...e}:null,this.scheduleRender()}catch(t){this.handleError(t)}}calculateProgressFromEvent(e){const t=this.canvas.getBoundingClientRect();return normalizeProgress((e.clientX-t.left)/t.width)}calculateProgressFromTouch(e){const t=this.canvas.getBoundingClientRect();return normalizeProgress((e.clientX-t.left)/t.width)}drawWaveform(){if(!this.isDestroyed){this.emit("renderStart",void 0);try{const{backgroundColor:e,color:t,progress:s}=this.options,i=this.canvas.width/this.devicePixelRatio,r=this.canvas.height/this.devicePixelRatio;if(this.ctx.clearRect(0,0,i,r),this.drawWaveformWithColor(e),s>0){this.ctx.save();const e=i*s;this.ctx.beginPath(),this.ctx.rect(0,0,e,r),this.ctx.clip(),this.drawWaveformWithColor(t),this.ctx.restore()}if(this.options.progressLine&&s>0){const e=i*s;!function drawProgressLine(e,t,s,i){const{color:r,heightPercent:o,position:n,style:h,width:a}=i,c=s*o;e.save(),e.strokeStyle=r,e.lineWidth=a,e.lineCap="round";const{endY:l,startY:d}=function calculateLineDimensions(e,t,s){switch(s){case"bottom":return{endY:t,startY:t-e};case"top":return{endY:e,startY:0};default:return{endY:(t+e)/2,startY:(t-e)/2}}}(c,s,n);if("solid"!==h){const[t,s]="dashed"===h?[8,4]:[2,2];e.setLineDash([t,s])}e.beginPath(),e.moveTo(t,d),e.lineTo(t,l),e.stroke(),e.restore()}(this.ctx,e,r,this.options.progressLine)}this.emit("renderComplete",void 0)}catch(e){this.handleError(e)}}}drawWaveformWithColor(e){const{amplitude:t=1,barWidth:s,borderColor:i,borderRadius:r,borderWidth:o=0,gap:n=0,position:h}=this.options,a=this.canvas.width/this.devicePixelRatio,c=this.canvas.height/this.devicePixelRatio,l=o,d=a-2*o*l,u=s+2*o+n,m=Math.floor(d/u),g=Math.max(1,m),v=this.peaks.length/g;this.ctx.fillStyle=e,this.ctx.strokeStyle=i,this.ctx.lineWidth=o;for(let f=0;f<g;f++){const e=Math.floor(f*v),i=Math.abs(this.peaks[e]||0),n=l+f*u,{height:a,y:d}=calculateBarDimensions(i,c,t,h);this.ctx.beginPath(),r>0?this.ctx.roundRect(n,d,s,a,r):this.ctx.rect(n,d,s,a),this.ctx.fill(),o>0&&this.ctx.stroke()}}resizeCanvas(){!function resizeCanvas(e,t){const s=e.getBoundingClientRect(),i=s.width*t,r=s.height*t;return e.width===i&&e.height===r||(e.width=i,e.height=r),{height:s.height,width:s.width}}(this.canvas,this.devicePixelRatio),this.ctx.setTransform(1,0,0,1,0,0),this.ctx.scale(this.devicePixelRatio,this.devicePixelRatio),this.setupContext()}scheduleRender(){this.frameRequest&&cancelAnimationFrame(this.frameRequest),this.frameRequest=requestAnimationFrame((()=>this.drawWaveform()))}setupContext(){!function setupCanvasContext(e,t=!0){e.imageSmoothingEnabled=t,t&&(e.imageSmoothingQuality="high")}(this.ctx,this.options.smoothing)}},e.getPeaksFromAudioBuffer=function getPeaksFromAudioBuffer(e,t){const s=e.getChannelData(0),i=new Array(t),r=Math.floor(s.length/t);for(let o=0;o<t;o++){const e=o*r,t=e+r;let n=0;for(let i=e;i<t;i++){const e=Math.abs(s[i]);e>n&&(n=e)}i[o]=n}return normalizePeaks(i)},Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),e}({});