/** * 音频可视化 * @param audioDom * @param canvasDom * @param fftSize 2的幂数,最小为32 * 注意 由于ios低版本必须在用户操作之后才能初始化 createMediaElementSource 所以必须在用户操作之后初始化 */ export default function audioVisualDraw(audioDom: HTMLAudioElement, canvasDom: HTMLCanvasElement, fftSize = 128) { type propsType = { canvWidth: number; canvHeight: number; canvFillColor: string; lineColor: string; lineGap: number } // canvas const canvasCtx = canvasDom.getContext("2d")! const { width, height } = canvasDom.getBoundingClientRect() canvasDom.width = width canvasDom.height = height // audio //const audioCtx = new AudioContext() //const source = audioCtx.createMediaElementSource(audioDom) //const analyser = audioCtx.createAnalyser() //analyser.fftSize = fftSize //source?.connect(analyser) //analyser.connect(audioCtx.destination) //const dataArray = new Uint8Array(fftSize / 2) const draw = (data: Uint8Array, ctx: CanvasRenderingContext2D, { lineGap, canvWidth, canvHeight, canvFillColor, lineColor }: propsType) => { if (!ctx) return const w = canvWidth const h = canvHeight fillCanvasBackground(ctx, w, h, canvFillColor) // 可视化 const dataLen = data.length let step = (w / 2 - lineGap * dataLen) / dataLen step < 1 && (step = 1) const midX = w / 2 const midY = h / 2 let xLeft = midX for (let i = 0; i < dataLen; i++) { const value = data[i] const percent = value / 255 // 最大值为255 const barHeight = percent * midY canvasCtx.fillStyle = lineColor // 中间加间隙 if (i === 0) { xLeft -= lineGap / 2 } canvasCtx.fillRect(xLeft - step, midY - barHeight, step, barHeight) canvasCtx.fillRect(xLeft - step, midY, step, barHeight) xLeft -= step + lineGap } let xRight = midX for (let i = 0; i < dataLen; i++) { const value = data[i] const percent = value / 255 // 最大值为255 const barHeight = percent * midY canvasCtx.fillStyle = lineColor if (i === 0) { xRight += lineGap / 2 } canvasCtx.fillRect(xRight, midY - barHeight, step, barHeight) canvasCtx.fillRect(xRight, midY, step, barHeight) xRight += step + lineGap } } const fillCanvasBackground = (ctx: CanvasRenderingContext2D, w: number, h: number, colors: string) => { ctx.clearRect(0, 0, w, h) ctx.fillStyle = colors ctx.fillRect(0, 0, w, h) } const requestAnimationFrameFun = () => { // requestAnimationFrame(() => { // //analyser?.getByteFrequencyData(dataArray) // draw(generateMixedData(48), canvasCtx, { // lineGap: 2, // canvWidth: width, // canvHeight: height, // canvFillColor: "transparent", // lineColor: "rgba(255, 255, 255, 0.7)" // }) // if (!isPause) { // requestAnimationFrameFun() // } // }) const _time = setInterval(() => { if (isPause) { clearInterval(_time) return } //analyser?.getByteFrequencyData(dataArray) draw(generateMixedData(38), canvasCtx, { lineGap: 3, canvWidth: width, canvHeight: height, canvFillColor: "transparent", lineColor: "rgba(255, 255, 255, 0.7)" }) }, 300); } let isPause = true const playVisualDraw = () => { //audioCtx.resume() // 重新更新状态 加了暂停和恢复音频音质发生了变化 所以这里取消了 isPause = false requestAnimationFrameFun() } const pauseVisualDraw = () => { isPause = true requestAnimationFrame(()=>{ canvasCtx.clearRect(0, 0, width, height); }) //audioCtx?.suspend() // 暂停 加了暂停和恢复音频音质发生了变化 所以这里取消了 // source?.disconnect() // analyser?.disconnect() } return { playVisualDraw, pauseVisualDraw } } export function generateMixedData(size:number) { const dataArray = new Uint8Array(size); const baseNoiseAmplitude = 30; const minFrequency = 0.01; const maxFrequency = 0.2; const minAmplitude = 50; const maxAmplitude = 150; for (let i = 0; i < size; i++) { const frequency = minFrequency + Math.random() * (maxFrequency - minFrequency); const amplitude = minAmplitude + Math.random() * (maxAmplitude - minAmplitude); const wave = amplitude * (0.5 + 0.5 * Math.sin(frequency * i)); const noise = Math.floor(Math.random() * baseNoiseAmplitude) - baseNoiseAmplitude / 2; dataArray[i] = Math.min(255, Math.max(0, Math.floor(wave + noise))); } return dataArray; }