// 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 = 'black' 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;