/** * 音频可视化 * @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(dataArray, canvasCtx, { lineGap: 2, canvWidth: width, canvHeight: height, canvFillColor: "transparent", lineColor: "rgba(255, 255, 255, 0.7)" }) if (!isPause) { requestAnimationFrameFun() } }) } let isPause = true const playVisualDraw = () => { //audioCtx.resume() // 重新更新状态 加了暂停和恢复音频音质发生了变化 所以这里取消了 isPause = false requestAnimationFrameFun() } const pauseVisualDraw = () => { isPause = true //audioCtx?.suspend() // 暂停 加了暂停和恢复音频音质发生了变化 所以这里取消了 // source?.disconnect() // analyser?.disconnect() } return { playVisualDraw, pauseVisualDraw } }