// Override the startRecording method so we can pass the desired stream // Checkout: https://github.com/katspaugh/wavesurfer.js/blob/fa2bcfe/src/plugins/record.ts import RecordPlugin from "wavesurfer.js/dist/plugins/record"; const MIME_TYPES = [ "audio/webm", "audio/wav", "audio/mpeg", "audio/mp4", "audio/mp3", ]; const findSupportedMimeType = () => MIME_TYPES.find((mimeType) => MediaRecorder.isTypeSupported(mimeType)); class CustomRecordPlugin extends RecordPlugin { static create(options) { return new CustomRecordPlugin(options || {}); } render(stream) { if (!this.wavesurfer) return () => undefined; const container = this.wavesurfer.getWrapper(); const canvas = document.createElement("canvas"); canvas.width = container.clientWidth; canvas.height = container.clientHeight; canvas.style.zIndex = "10"; container.appendChild(canvas); const canvasCtx = canvas.getContext("2d"); const audioContext = new AudioContext(); const source = audioContext.createMediaStreamSource(stream); const analyser = audioContext.createAnalyser(); analyser.fftSize = 2 ** 5; source.connect(analyser); const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); let animationId, previousTimeStamp; const BUFFER_SIZE = 2 ** 8; const dataBuffer = new Array(BUFFER_SIZE).fill(canvas.height); const drawWaveform = (timeStamp) => { if (!canvasCtx) return; analyser.getByteTimeDomainData(dataArray); canvasCtx.clearRect(0, 0, canvas.width, canvas.height); canvasCtx.fillStyle = "#cc3347"; if (previousTimeStamp === undefined) { previousTimeStamp = timeStamp; dataBuffer.push(Math.min(...dataArray)); dataBuffer.splice(0, 1); } const elapsed = timeStamp - previousTimeStamp; if (elapsed > 10) { previousTimeStamp = timeStamp; dataBuffer.push(Math.min(...dataArray)); dataBuffer.splice(0, 1); } // Drawing const sliceWidth = canvas.width / dataBuffer.length; let x = 0; for (let i = 0; i < dataBuffer.length; i++) { const valueNormalized = dataBuffer[i] / canvas.height; const y = (valueNormalized * canvas.height) / 2; const sliceHeight = canvas.height + 1 - y * 2; canvasCtx.fillRect(x, y, (sliceWidth * 2) / 3, sliceHeight); x += sliceWidth; } animationId = requestAnimationFrame(drawWaveform); }; drawWaveform(); return () => { if (animationId) { cancelAnimationFrame(animationId); } if (source) { source.disconnect(); source.mediaStream.getTracks().forEach((track) => track.stop()); } if (audioContext) { audioContext.close(); } canvas?.remove(); }; } startRecording(stream) { this.preventInteraction(); this.cleanUp(); const onStop = this.render(stream); const mediaRecorder = new MediaRecorder(stream, { mimeType: this.options.mimeType || findSupportedMimeType(), audioBitsPerSecond: this.options.audioBitsPerSecond, }); const recordedChunks = []; mediaRecorder.addEventListener("dataavailable", (event) => { if (event.data.size > 0) { recordedChunks.push(event.data); } }); mediaRecorder.addEventListener("stop", () => { onStop(); this.loadBlob(recordedChunks, mediaRecorder.mimeType); this.emit("stopRecording"); }); mediaRecorder.start(); this.emit("startRecording"); this.mediaRecorder = mediaRecorder; } } export default CustomRecordPlugin;