audioVisualDraw.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  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(generateMixedData(48), 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. const _time = setInterval(() => {
  82. if (isPause) {
  83. clearInterval(_time)
  84. return
  85. }
  86. //analyser?.getByteFrequencyData(dataArray)
  87. draw(generateMixedData(38), canvasCtx, {
  88. lineGap: 3,
  89. canvWidth: width,
  90. canvHeight: height,
  91. canvFillColor: "transparent",
  92. lineColor: "rgba(255, 255, 255, 0.7)"
  93. })
  94. }, 300);
  95. }
  96. let isPause = true
  97. const playVisualDraw = () => {
  98. //audioCtx.resume() // 重新更新状态 加了暂停和恢复音频音质发生了变化 所以这里取消了
  99. isPause = false
  100. requestAnimationFrameFun()
  101. }
  102. const pauseVisualDraw = () => {
  103. isPause = true
  104. requestAnimationFrame(()=>{
  105. canvasCtx.clearRect(0, 0, width, height);
  106. })
  107. //audioCtx?.suspend() // 暂停 加了暂停和恢复音频音质发生了变化 所以这里取消了
  108. // source?.disconnect()
  109. // analyser?.disconnect()
  110. }
  111. return {
  112. playVisualDraw,
  113. pauseVisualDraw
  114. }
  115. }
  116. export function generateMixedData(size:number) {
  117. const dataArray = new Uint8Array(size);
  118. const baseNoiseAmplitude = 30;
  119. const minFrequency = 0.01;
  120. const maxFrequency = 0.2;
  121. const minAmplitude = 50;
  122. const maxAmplitude = 150;
  123. for (let i = 0; i < size; i++) {
  124. const frequency = minFrequency + Math.random() * (maxFrequency - minFrequency);
  125. const amplitude = minAmplitude + Math.random() * (maxAmplitude - minAmplitude);
  126. const wave = amplitude * (0.5 + 0.5 * Math.sin(frequency * i));
  127. const noise = Math.floor(Math.random() * baseNoiseAmplitude) - baseNoiseAmplitude / 2;
  128. dataArray[i] = Math.min(255, Math.max(0, Math.floor(wave + noise)));
  129. }
  130. return dataArray;
  131. }