ChatGPT多模态前端自建日志(废弃日志记录-音频可视化)

被chatgpt的语音对话迷了眼 ,就想着也做个动画然后通话。但是做好了发现实际上自己想要的是在输入区域内语音、上传文件、输入文本同步处理,动画效果对于这个项目而言完全是多余的。不过都做出来了,就放出来,有需要的小伙伴自己玩玩吧。
注释分别是文件和麦克风的互动,根据自己需要选择取消注释。

   <canvas></canvas>
  <audio src="" controls></audio>
  <button id="audiopbtn" class="btn"> <img src="" alt="audio icon"></button>
body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100vh;
  margin: 0;
  background-color: black;
}

.btn {
  background-color: white;
  color: rgb(15, 14, 14);
  font-size: 15px;
  height: auto;
  padding: 5px 15px;
  border-radius: 20px;
  margin-top: 10px;
  cursor: pointer;
  margin-bottom: 10px;
  margin: 5px 0;
  border: 1px solid #ccc;
  box-shadow: 0 4px #999;
}

.btn:hover {
  background-color: rgb(212, 210, 210);
  box-shadow: 0 2px #666;
  transform: translateY(2px);
}

.btn:active {
  box-shadow: 0 1px #666;
  transform: translateY(4px);
}

canvas {
  width: 500px;
  height: 400px;
}

audio {
  margin-top: 20px;
}
document.addEventListener('DOMContentLoaded', (event) => {
  const audioEle = document.querySelector('audio');
  const cvs = document.querySelector('canvas');
  const ctx = cvs.getContext('2d');

  function initCvs() {
    const size = Math.min(window.innerWidth, window.innerHeight) * 0.8; // 80% of viewport
    cvs.width = size * devicePixelRatio;
    cvs.height = size * devicePixelRatio;
    cvs.style.width = `${size}px`;
    cvs.style.height = `${size}px`;
  }

  function draw(datas, maxValue) {
    const r = cvs.width / 4 + 20 * devicePixelRatio;
    const center = cvs.width / 2;
    ctx.clearRect(0, 0, cvs.width, cvs.height);

    const hslStep = 360 / datas.length;
    const maxLen = cvs.width / 2 - r;
    const minLen = 2 * devicePixelRatio;

    for (let i = 0; i < datas.length; i++) {
      const value = datas[i] / maxValue;
      const len = minLen + value * (maxLen - minLen);
      const angle = (i * 2 * Math.PI) / datas.length;
      const x1 = center + r * Math.cos(angle);
      const y1 = center + r * Math.sin(angle);
      const x2 = center + (r + len) * Math.cos(angle);
      const y2 = center + (r + len) * Math.sin(angle);

      ctx.beginPath();
      ctx.moveTo(x1, y1);
      ctx.lineTo(x2, y2);
      ctx.strokeStyle = `hsl(${i * hslStep}, 100%, 50%)`;
      ctx.lineWidth = 2 * devicePixelRatio;
      ctx.stroke();
    }
  }

  initCvs();
  // const arr = new Array(100).fill(0).map(() => Math.random() * 255);
  draw(new Array(256).fill(0), 255);
  let isInit = false, analyser, buffer, ws;
  let isMicActive = false; // 麦克风的初始状态为关闭
  let stream = null; // 用于存储音频流
  let source = null;
  // 对于音频来源文件的处理
  // audioEle.onplay = function () {
  //   if (isInit) {
  //     return;
  //   }
  //   isInit = true;
  //   const audioCtx = new AudioContext();
  //   const source = audioCtx.createMediaElementSource(audioEle); // 来源节点
  //   analyser = audioCtx.createAnalyser();  // 分析器节点

  //   // 分析频域数据
  //   analyser.fftSize = 512;  // 值越大,越精细
  //   buffer = new Uint8Array(analyser.frequencyBinCount);
  //   analyser.getByteFrequencyData(buffer); // 获取频域数据

  //   source.connect(analyser);
  //   analyser.connect(audioCtx.destination); // 连接到喇叭
  // };

  // // 对于麦克风音频的处理
  // navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
  //   if (isInit) {
  //     return;
  //   }
  //   isInit = true;
  //   const audioCtx = new AudioContext();
  //   const source = audioCtx.createMediaStreamSource(stream); // 来自于流
  //   analyser = audioCtx.createAnalyser();  // 分析器节点

  //   // 分析频域数据
  //   analyser.fftSize = 512;  // 值越大,越精细
  //   buffer = new Uint8Array(analyser.frequencyBinCount);
  //   analyser.getByteFrequencyData(buffer); // 获取频域数据

  //   source.connect(analyser);
  //   // analyser.connect(audioCtx.destination); // 连接到喇叭
  // });

  function toggleMicrophone() {
    if (!isMicActive) {
      // 开启麦克风
      navigator.mediaDevices.getUserMedia({ audio: true }).then(s => {
        isMicActive = true; // 更新麦克风状态
        stream = s;

        ws = new WebSocket("ws://localhost:9090/api/audio");

        ws.onopen = function (event) {
          console.log('WebSocket connected');
        };

        ws.onerror = function (error) {
          ws.close();
          console.error('WebSocket Error:', error);
        };

        if (!isInit) {
          audioCtx = new AudioContext();
          source = audioCtx.createMediaStreamSource(stream); // 来自于流
          analyser = audioCtx.createAnalyser();  // 分析器节点

          // 分析频域数据
          analyser.fftSize = 512; // 值越大,越精细
          buffer = new Uint8Array(analyser.frequencyBinCount);
          console.log("buffer:", buffer);
          analyser.getByteFrequencyData(buffer); // 获取频域数据

          source.connect(analyser);
          // analyser.connect(audioCtx.destination); // 如果你需要将音频输出到喇叭,取消注释这行
          isInit = true;
        }
        update();
      }).catch(error => {
        console.error("Error accessing the microphone", error);
      });
    } else {
      // 关闭麦克风
      if (stream) {
        const tracks = stream.getTracks();
        tracks.forEach(track => track.stop());
        isMicActive = false; // 更新麦克风状态
        isInit = false; // 重置初始化状态,以便下次重新初始化
      }
      // 关闭 WebSocket 连接
      if (ws) {
        ws.close();
        console.log('WebSocket disconnected');
      }
    }
  }

  document.getElementById('audiopbtn').addEventListener('click', toggleMicrophone);

  function update() {
    requestAnimationFrame(update);
    if (!isInit || !ws || ws.readyState !== WebSocket.OPEN) {
      return;
    }
    if (ws.readyState === WebSocket.OPEN) {
      const arrayBuffer = buffer.buffer; // 获取 Uint8Array 的 ArrayBuffer
      ws.send(arrayBuffer);
    }
    analyser.getByteFrequencyData(buffer);
    const offset = Math.floor((buffer.length * 2) / 3);
    const data = new Array(offset * 2);
    for (let i = 0; i < offset; i++) {
      data[i] = data[data.length - i - 1] = buffer[i];
      // console.log("data:", data[i]);
    }
    draw(data, 255);
    // console.log(buffer);
  }


  // update();
});

4 个赞

好厉害,这个动画自己实现的吗?

1 个赞

好活

不完全是,刷视频的时候刷到一个老师的视频觉得讲的挺不错就借鉴了,没有他做的好看,复杂的没有实现,:grin:

大佬nb

好活