使用音频:进度和数据可视化



朋友们,美好的一天!

我想分享音频方面的经验。“音频”是指HTMLAudioElement和Web Audio API。

我们做什么?

我们将为某首曲目创建一个播放器(在以下文章之一中,关于成熟的播放器)。

条件:

  • 单击按钮或通过拖放从硬盘上任何位置下载文件的功能。
  • 圆形图形和文本进度指示器。
  • 音量的文本指示符。
  • 可视化音频数据。
  • 使用键盘控制播放器。

网络上充斥着HTMLAudioElementWAAPI的资料,因此我将重点介绍实际组件。除了音频,我们还将使用拖放canvas

没有进一步介绍...

是的,我几乎忘记了:我把哈布罗夫斯克的一位公民的工作作为“可视化工具”的基础。我无法通过搜索找到它。请提供参考。

我们的标记如下所示:

<p>click or drag</p>
<div dropzone>
    <img src="https://thebestcode.ru/media/audioProgress&Visualizer/plus.png" alt="#">
    <input type="file" accept="audio/*">
</div>
<canvas></canvas>

我们有一个工具提示(段落),一个按钮(图片+“输入”,在容器中具有accept属性,并具有dropzone属性
和画布。

如您所见,没有什么异常。

风格上也没有什么超自然的。

CSS:
@font-face {
  font-family: "Nova Mono", monospace;
  src: url("https://thebestcode.ru/media/audioProgress&Visualizer/font.ttf");
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  height: 100vh;
  background: radial-gradient(circle, #666, #222);
  display: flex;
  justify-content: center;
  align-items: center;
}

p {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -70px);
  color: #ddd;
  text-align: center;
  text-transform: uppercase;
  font-family: "Nova Mono", monospace;
  font-size: 0.8em;
  font-weight: bold;
  letter-spacing: 2px;
  user-select: none;
}

span {
  display: block;
  font-size: 1.6em;
}

div {
  width: 100px;
  height: 100px;
  display: flex;
  justify-content: center;
  align-items: center;
  border: 1px dashed #ddd;
  border-radius: 10%;
  cursor: pointer;
}

img {
  width: 70px;
  height: 70px;
  filter: invert();
}

input {
  display: none;
}

canvas {
  display: none;
}


我们传递给JS。

我们声明主要变量:

let dropZone = document.querySelector("div"),
    input = document.querySelector("input"),
    file,
    text,
    progress,
    volume,
    audio,
    //   
    frequencyArray;

我们使用拖放操作:

// "" 
dropZone.ondrop = e => {
    //     
    e.preventDefault();
    //  
    if (e.dataTransfer.items[0].kind == "file") {
        //  
        file = e.dataTransfer.items[0].getAsFile();
    } else return;
    //  
    playTrack(file);
};

//   ""
dropZone.ondragover = e => {
    //     
    e.preventDefault();
};

//   
dropZone.onclick = () => {
    //   
    input.click();
    //   
    input.onchange = () => {
        //  
        file = input.files[0];
        //  
        playTrack(file);
    };
};

我们继续前进。

为画布声明变量:

let C = document.querySelector("canvas"),
    $ = C.getContext("2d"),
    W = (C.width = innerWidth),
    H = (C.height = innerHeight),
    centerX = W / 2,
    centerY = H / 2,
    radius,
    //         
    piece,
    //  
    bars = 200,
    x,
    y,
    xEnd,
    yEnd,
    //  
    barWidth = 2,
    //  
    barHeight,
    //  
    lineColor;

我们继续执行main函数(所有进一步的代码将在此函数中):

function playTrack(file) {
    //  
}

我们删除区域(不再需要它),更改段落的文本,初始化声音和进度的变量:

dropZone.style.display = "none";

text = document.querySelector("p");

text.style.transform = "translate(-50%,-50%)";

text.innerHTML = `progress: <span class="progress"></span> <br> volume: <span class="volume"></span>`;

volume = document.querySelector(".volume");

progress = document.querySelector(".progress");

令人联想到声音:

audio = new Audio();
//     ,    
//        ()  
context = new AudioContext();

//    ,   (..  )      
//        
analyser = context.createAnalyser();

//  URL.createObjectURL()  DOMString,  URL    ,   
//          
//   URL -  
audio.src = URL.createObjectURL(file);

//   
source = context.createMediaElementSource(audio);

//     
source.connect(analyser);

//    ""  -   
analyser.connect(context.destination);

//           
//      
/*let bufferLength = analyser.frequencyBinCount;
let frequencyArray = new Uint8Array(bufferLength);*/
frequencyArray = new Uint8Array(analyser.frequencyBinCount);

//  
audio.play();

//   
audio.loop = true;

添加控制播放器的功能(我们没有按钮,因此您只能使用键盘来控制播放器):

document.addEventListener("keydown", e => {
    //   try/catch    Chrome,     
    //   try/catch       (console.log(audio.volume))   
    //    0  1 (       0  1)   ,     if(audio.volume>0 && audio.volume<1)
    //    ""   0.1  0.9   ,       
    //     ,  
    try {
        //    
        e.preventDefault()

        // 
        if (e.keyCode == 32) {
            // /
            audio.paused ? audio.play() : audio.pause();
        // enter
        } else if (e.keyCode == 13) {
            // 
            audio.load();
        //  
        } else if (e.keyCode == 39) {
            //   + 10 
            audio.currentTime += 10;
        //  
        } else if (e.keyCode == 37) {
            //   - 10 
            audio.currentTime -= 10;
        //  
        } else if (e.keyCode == 40) {
            //   - 10%
            audio.volume -= 0.1;
        //  
        } else if (e.keyCode == 38) {
            //   + 10%
            audio.volume += 0.1;
        }
    //  
    } catch {
        return;
    }
});

//  
console.log(
    " Use Keyboard: \n Space to Play/Pause \n Enter to Stop \n Arrows to Change \n Time and Volume"
);

下一部分是动画。我们调用相应的函数:

startAnimation();

该函数本身如下:

function startAnimation() {
    //  
    C.style.display = "block";

    //    (   /  )
    piece = audio.currentTime / audio.duration;

    //   
    //     :   ,    
    radius = 105;
    
    //  
    $.clearRect(0, 0, W, H);
    
    //   
    $.beginPath();
    $.arc(centerX, centerY, radius, 0, Math.PI * (2 * piece));
    $.lineWidth = 30;
    $.stroke();
    
    //   
    volume.innerText = Math.trunc(audio.volume * 100) + "%";
    
    //   
    progress.innerText = Math.trunc(piece * 100) + "%";

    //      frequencyArray
    analyser.getByteFrequencyData(frequencyArray);
    //     
    for (let i = 0; i < bars; i++) {
        //  
        radius = 120;

        //     
        rads = Math.PI * 2 / bars;

        //   
        barHeight = frequencyArray[i] * 0.6;
        
        //   0   
        x = centerX + Math.cos(rads * i) * radius;
        y = centerY + Math.sin(rads * i) * radius;
        xEnd = centerX + Math.cos(rads * i) * (radius + barHeight);
        yEnd = centerY + Math.sin(rads * i) * (radius + barHeight);
        
        //  
        drawBar(x, y, xEnd, yEnd, barWidth, frequencyArray[i]);
    }

    //  
    requestAnimationFrame(startAnimation);
}

最后-渲染列:

//      ,   
function drawBar(x1, y1, x2, y2, width, frequency) {
    //     -  -
    lineColor = "rgb(" + frequency + ", " + frequency + ", " + 205 + ")";
    
    //  
    $.strokeStyle = lineColor;
    $.lineWidth = width;
    $.beginPath();
    $.moveTo(x1, y1);
    $.lineTo(x2, y2);
    $.stroke();
}

结果可以在这里看到

感谢您的关注。

Source: https://habr.com/ru/post/undefined/


All Articles