audioVisualDraw.ts 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. /**
  2. * 音频可视化
  3. * @param audioDom
  4. * @param canvasDom
  5. * @param fftSize 2的幂数,最小为32
  6. * 注意 由于ios低版本必须在用户操作之后才能初始化 createMediaElementSource 所以必须在用户操作之后初始化
  7. */
  8. export default function audioVisualDraw(audioDom: HTMLAudioElement, canvasDom: HTMLCanvasElement, fftSize = 128) {
  9. type propsType = { canvWidth: number; canvHeight: number; canvFillColor: string; lineColor: string; lineGap: number }
  10. // canvas
  11. const canvasCtx = canvasDom.getContext("2d")!
  12. const { width, height } = canvasDom.getBoundingClientRect()
  13. canvasDom.width = width
  14. canvasDom.height = height
  15. // audio
  16. const audioCtx = new AudioContext()
  17. const source = audioCtx.createMediaElementSource(audioDom)
  18. const analyser = audioCtx.createAnalyser()
  19. analyser.fftSize = fftSize
  20. source?.connect(analyser)
  21. analyser.connect(audioCtx.destination)
  22. const dataArray = new Uint8Array(fftSize / 2)
  23. const draw = (data: Uint8Array, ctx: CanvasRenderingContext2D, { lineGap, canvWidth, canvHeight, canvFillColor, lineColor }: propsType) => {
  24. if (!ctx) return
  25. const w = canvWidth
  26. const h = canvHeight
  27. fillCanvasBackground(ctx, w, h, canvFillColor)
  28. // 可视化
  29. const dataLen = data.length
  30. let step = (w / 2 - lineGap * dataLen) / dataLen
  31. step < 1 && (step = 1)
  32. const midX = w / 2
  33. const midY = h / 2
  34. let xLeft = midX
  35. for (let i = 0; i < dataLen; i++) {
  36. const value = data[i]
  37. const percent = value / 255 // 最大值为255
  38. const barHeight = percent * midY
  39. canvasCtx.fillStyle = lineColor
  40. // 中间加间隙
  41. if (i === 0) {
  42. xLeft -= lineGap / 2
  43. }
  44. canvasCtx.fillRect(xLeft - step, midY - barHeight, step, barHeight)
  45. canvasCtx.fillRect(xLeft - step, midY, step, barHeight)
  46. xLeft -= step + lineGap
  47. }
  48. let xRight = midX
  49. for (let i = 0; i < dataLen; i++) {
  50. const value = data[i]
  51. const percent = value / 255 // 最大值为255
  52. const barHeight = percent * midY
  53. canvasCtx.fillStyle = lineColor
  54. if (i === 0) {
  55. xRight += lineGap / 2
  56. }
  57. canvasCtx.fillRect(xRight, midY - barHeight, step, barHeight)
  58. canvasCtx.fillRect(xRight, midY, step, barHeight)
  59. xRight += step + lineGap
  60. }
  61. }
  62. const fillCanvasBackground = (ctx: CanvasRenderingContext2D, w: number, h: number, colors: string) => {
  63. ctx.clearRect(0, 0, w, h)
  64. ctx.fillStyle = colors
  65. ctx.fillRect(0, 0, w, h)
  66. }
  67. const requestAnimationFrameFun = () => {
  68. requestAnimationFrame(() => {
  69. analyser?.getByteFrequencyData(dataArray)
  70. draw(dataArray, canvasCtx, {
  71. lineGap: 2,
  72. canvWidth: width,
  73. canvHeight: height,
  74. canvFillColor: "transparent",
  75. lineColor: "rgba(255, 255, 255, 0.7)"
  76. })
  77. if (!isPause) {
  78. requestAnimationFrameFun()
  79. }
  80. })
  81. }
  82. let isPause = true
  83. const playVisualDraw = () => {
  84. //audioCtx.resume() // 重新更新状态 加了暂停和恢复音频音质发生了变化 所以这里取消了
  85. isPause = false
  86. requestAnimationFrameFun()
  87. }
  88. const pauseVisualDraw = () => {
  89. isPause = true
  90. //audioCtx?.suspend() // 暂停 加了暂停和恢复音频音质发生了变化 所以这里取消了
  91. // source?.disconnect()
  92. // analyser?.disconnect()
  93. }
  94. return {
  95. playVisualDraw,
  96. pauseVisualDraw
  97. }
  98. }