Skip to content

JavaScript 音频可视化

Posted on:2021年1月7日 at 14:27

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
  width: 100%;
  overflow: hidden;
}

#upload {
  position: fixed;
  top: 0;
  left: 0;
}

audio {
  position: fixed;
  top: 0;
  right: 0;
}
<input type="file" id="upload" accept="audio/mpeg" />
const upload = document.querySelector("#upload");

upload.addEventListener("change", e => {
  const [file] = e.target.files;
  if (!file) upload.value = "";
  const { type } = file;
  if (!type.includes("audio")) {
    alert("仅支持音频格式文件");
    upload.value = "";
    return false;
  }
  const url = URL.createObjectURL(file);
  draw(url);
});

function draw(url) {
  const { clientWidth: width, clientHeight: height } = document.body;
  const audio = new Audio();
  audio.src = url;
  audio.controls = true;
  audio.load();
  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;
  document.body.appendChild(audio);
  document.body.appendChild(canvas);
  const canvasCtx = canvas.getContext("2d");
  const audioCtx = new AudioContext();
  const sourceNode = audioCtx.createMediaElementSource(audio);
  // 音频分析器
  const analyser = audioCtx.createAnalyser();
  analyser.fftSize = 128; // FFT
  sourceNode.connect(analyser);
  analyser.connect(audioCtx.destination);
  const len = analyser.frequencyBinCount;
  const buffer = new Uint8Array(len);
  const gap = 2; // 间隙
  const pillarWidth = width / len - gap;
  const gradient = canvasCtx.createLinearGradient(0, 0, 0, 500);
  gradient.addColorStop(0.8, "#1eec1e");
  gradient.addColorStop(0.5, "#c97a3d");
  gradient.addColorStop(0, "#f00f00");

  const render = () => {
    canvasCtx.fillStyle = "#fff";
    canvasCtx.fillRect(0, 0, width, height);
    analyser.getByteFrequencyData(buffer);
    let x = 0;
    buffer.forEach(v => {
      const pillarHeight = (v / 255) * height;
      canvasCtx.fillStyle = gradient;
      canvasCtx.fillRect(
        x,
        height - (pillarHeight || 2),
        pillarWidth,
        pillarHeight || 2
      );
      x += pillarWidth + gap;
    });
    requestAnimationFrame(render);
  };
  render();
  audio.play();
}