马黑黑 发表于 2024-3-19 12:17

实现本地可视化音频播放效果实例

<style>
.mum { position: relative; margin: 0; padding: 10px; font: normal 16px/20px Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; color: black; background: rgba(240, 240, 240,.95); box-shadow: 2px 2px 4px gray; border: thick groove lightblue; border-radius: 6px; }
.mum ::selection { background-color: rgba(0,100,100,.35); }
.mum div { margin: 0; padding: 0; }
.mum cl-cd { display: block; position: relative; margin: 0 0 0 50px; padding: 0 0 0 10px; white-space: pre-wrap; overflow-wrap: break-word; border-left: 1px solid silver; }
.mum cl-cd::before { position: absolute; content: attr(data-idx); width: 50px; color: gray; text-align: right; transform: translate(-70px); }
.tRed { color: red; }
.tBlue { color: blue; }
.tGreen { color: green; }
.tDarkRed { color: darkred; }
.tMagenta { color: magenta; }
</style>

<p>本实例需要本地能够运行虚拟服务器。建议使用 PHPTS ,运行后可在浏览器使用 127.0.0.0 访问本地虚拟网站。以下代码可以保存在PHPTS安装目录下 X:\phpts\data\wwwroot 的一个子文件夹,比如 text 文件夹,代码保存为 123.html 或是别的名称,然后在浏览器地址栏输入 127.0.0.1/test/123.html 后回车,即可查看效果。<br><br></p>
<p>实例代码:<br><br></p>
<div class='mum'>
<cl-cd data-idx="1">&lt;!DOCTYPE html&gt;</cl-cd>
<cl-cd data-idx="2">&lt;<span class="tDarkRed">html</span> lang=<span class="tMagenta">"zh"</span>&gt;</cl-cd>
<cl-cd data-idx="3">&lt;<span class="tDarkRed">head</span>&gt;</cl-cd>
<cl-cd data-idx="4">&lt;<span class="tDarkRed">meta</span> charset=<span class="tMagenta">"utf-8"</span> /&gt;</cl-cd>
<cl-cd data-idx="5">&lt;<span class="tDarkRed">title</span>&gt;web audio API&lt;<span class="tDarkRed">/title</span>&gt;</cl-cd>
<cl-cd data-idx="6">&lt;<span class="tDarkRed">style</span>&gt;</cl-cd>
<cl-cd data-idx="7">&nbsp; &nbsp; #papa { <span class="tBlue">margin:</span> auto; <span class="tBlue">width:</span> 1024px; <span class="tBlue">height:</span> 640px; <span class="tBlue">border:</span>1px solid gray; <span class="tBlue">position:</span> relative; }</cl-cd>
<cl-cd data-idx="8">&nbsp; &nbsp; #canv { <span class="tBlue">position:</span> absolute; <span class="tBlue">left:</span> calc(50% - 150px); <span class="tBlue">bottom:</span> 20px; }</cl-cd>
<cl-cd data-idx="9">&lt;<span class="tDarkRed">/style</span>&gt;</cl-cd>
<cl-cd data-idx="10">&lt;<span class="tDarkRed">/head</span>&gt;</cl-cd>
<cl-cd data-idx="11">&lt;<span class="tDarkRed">body</span>&gt;</cl-cd>
<cl-cd data-idx="12">&nbsp;</cl-cd>
<cl-cd data-idx="13">&lt;<span class="tDarkRed">div</span> <span class="tRed">id</span>=<span class="tMagenta">"papa"</span>&gt;</cl-cd>
<cl-cd data-idx="14">&nbsp; &nbsp; &lt;<span class="tDarkRed">canvas</span> <span class="tRed">id</span>=<span class="tMagenta">"canv"</span> width=<span class="tMagenta">"300"</span> height=<span class="tMagenta">"200"</span> title=<span class="tMagenta">"点击播放"</span>&gt;&lt;<span class="tDarkRed">/canvas</span>&gt;</cl-cd>
<cl-cd data-idx="15">&lt;<span class="tDarkRed">/div</span>&gt;</cl-cd>
<cl-cd data-idx="16">&nbsp;</cl-cd>
<cl-cd data-idx="17">&lt;<span class="tDarkRed">script</span>&gt;</cl-cd>
<cl-cd data-idx="18">&nbsp;</cl-cd>
<cl-cd data-idx="19"><span class="tGreen">/* 创建h5 audio对象 */</span></cl-cd>
<cl-cd data-idx="20"><span class="tBlue">const</span> aud = <span class="tBlue">new</span> Audio();</cl-cd>
<cl-cd data-idx="21"><span class="tGreen">/* 同目录下音频文件 */</span></cl-cd>
<cl-cd data-idx="22">aud.src = <span class="tMagenta">'humanlegacy.mp3'</span>;</cl-cd>
<cl-cd data-idx="23">aud.load(); <span class="tGreen">/* 加载音乐 */</span></cl-cd>
<cl-cd data-idx="24">aud.loop = true; <span class="tGreen">/* 循环播放 */</span></cl-cd>
<cl-cd data-idx="25"><span class="tGreen">/* 下一句是自动播放音频 */</span></cl-cd>
<cl-cd data-idx="26"><span class="tGreen">/* aud.play(); */</span></cl-cd>
<cl-cd data-idx="27">&nbsp;</cl-cd>
<cl-cd data-idx="28"><span class="tGreen">/* 创建web audio api音频上下文 */</span></cl-cd>
<cl-cd data-idx="29"><span class="tBlue">let</span> AudioContext = <span class="tRed">window</span>.AudioContext || <span class="tRed">window</span>.webkitAudioContext;</cl-cd>
<cl-cd data-idx="30"><span class="tGreen">/* 音频上下文操作句柄 */</span></cl-cd>
<cl-cd data-idx="31"><span class="tBlue">let</span> Ac = <span class="tBlue">new</span> AudioContext;</cl-cd>
<cl-cd data-idx="32"><span class="tGreen">/* 创建音频分析器 */</span></cl-cd>
<cl-cd data-idx="33"><span class="tBlue">let</span> analyser = Ac.createAnalyser();</cl-cd>
<cl-cd data-idx="34"><span class="tGreen">/* 指定傅里叶快速变换参数 :作用于频率长度,2的非零幂,默认2048*/</span></cl-cd>
<cl-cd data-idx="35">analyser.fftSize = 256;</cl-cd>
<cl-cd data-idx="36"><span class="tGreen">/* 创建音源节点 :这里使用 audio 标签的音频输出作为音频来源 */</span></cl-cd>
<cl-cd data-idx="37"><span class="tBlue">let</span> source = Ac.createMediaElementSource(aud);</cl-cd>
<cl-cd data-idx="38"><span class="tGreen">/* 音频节点连接到分析器 */</span></cl-cd>
<cl-cd data-idx="39">source.connect(analyser);</cl-cd>
<cl-cd data-idx="40"><span class="tGreen">/* 分析器连接到音频输出对象(比如声卡) */</span></cl-cd>
<cl-cd data-idx="41">analyser.connect(Ac.destination);</cl-cd>
<div class="tGreen"><cl-cd data-idx="42">/* 频谱条数 :一般建议为 analyser.fftSize的一半,即下一句,</cl-cd>
<cl-cd data-idx="43">   <span class="tBlue">let</span> len = analyser.frequencyBinCount;</cl-cd>
<cl-cd data-idx="44">   综合考虑还是手工给出一个比fftSize小一点的数</cl-cd>
<cl-cd data-idx="45">*/</cl-cd></div>
<cl-cd data-idx="46"><span class="tBlue">let</span> len = 90;</cl-cd>
<cl-cd data-idx="47"><span class="tGreen">/* 创建8位无符号整型数组 : 它将从analyser获得实时音频数据 */</span></cl-cd>
<cl-cd data-idx="48"><span class="tBlue">let</span> output = <span class="tBlue">new</span> Uint8Array(len);</cl-cd>
<cl-cd data-idx="49"><span class="tGreen">/* 下面是画笔相关设定 */</span></cl-cd>
<cl-cd data-idx="50"><span class="tBlue">let</span> canvctx = canv.getContext(<span class="tMagenta">'2d'</span>); <span class="tGreen">/* 创建2d画笔 */</span></cl-cd>
<cl-cd data-idx="51"><span class="tBlue">let</span> ppWidth = canv.width / len; <span class="tGreen">/* 计算单个频谱宽度 */</span></cl-cd>
<cl-cd data-idx="52"><span class="tBlue">let</span> ppHeight, x; <span class="tGreen">/* 单个频谱高度和众频谱条的水平起始位置x */</span></cl-cd>
<cl-cd data-idx="53">&nbsp;</cl-cd>
<cl-cd data-idx="54"><span class="tGreen">/* 创建画笔渐变填充对象 */</span></cl-cd>
<cl-cd data-idx="55"><span class="tBlue">let</span> gradient = canvctx.createLinearGradient(0,0,0,200);</cl-cd>
<cl-cd data-idx="56">gradient.addColorStop(0,<span class="tMagenta">'rgba(128,0,0'</span>); <span class="tGreen">/* 红 */</span></cl-cd>
<cl-cd data-idx="57">gradient.addColorStop(.3,<span class="tMagenta">'rgba(255,165,0'</span>); <span class="tGreen">/* 橙 */</span></cl-cd>
<cl-cd data-idx="58">gradient.addColorStop(1,<span class="tMagenta">'rgba(0,128,0'</span>); <span class="tGreen">/* 绿 */</span></cl-cd>
<cl-cd data-idx="59">&nbsp;</cl-cd>
<cl-cd data-idx="60"><span class="tGreen">/* 根据音频数据绘制频谱 */</span></cl-cd>
<cl-cd data-idx="61">(<span class="tBlue">function</span> draw() {</cl-cd>
<cl-cd data-idx="62">&nbsp; &nbsp; canvctx.clearRect(0, 0, canv.width, canv.height);</cl-cd>
<cl-cd data-idx="63">&nbsp; &nbsp; <span class="tGreen">/* 分析器获取实时音频频率数据 :这里是 0 ~ 255 Uint8Array 并赋值给 output 数组 */</span></cl-cd>
<cl-cd data-idx="64">&nbsp; &nbsp; analyser.getByteFrequencyData(output);</cl-cd>
<cl-cd data-idx="65">&nbsp; &nbsp; x = 0.5; <span class="tGreen">/* 留一点点边 */</span></cl-cd>
<cl-cd data-idx="66">&nbsp; &nbsp; <span class="tGreen">/* 绘制 len 条柱状频谱 */</span></cl-cd>
<cl-cd data-idx="67">&nbsp; &nbsp; <span class="tBlue">for</span>(<span class="tBlue">let</span> i = 0; i &lt; len; i ++) {</cl-cd>
<cl-cd data-idx="68">&nbsp; &nbsp; &nbsp; &nbsp; ppHeight = output * .75 + ppWidth;</cl-cd>
<cl-cd data-idx="69">&nbsp; &nbsp; &nbsp; &nbsp; canvctx.fillStyle = gradient;</cl-cd>
<cl-cd data-idx="70">&nbsp; &nbsp; &nbsp; &nbsp; canvctx.fillRect(x, canv.height - ppHeight, ppWidth - 1, ppHeight);</cl-cd>
<cl-cd data-idx="71">&nbsp; &nbsp; &nbsp; &nbsp; x += ppWidth;</cl-cd>
<cl-cd data-idx="72">&nbsp; &nbsp; }</cl-cd>
<cl-cd data-idx="73">&nbsp; &nbsp; requestAnimationFrame(draw); <span class="tGreen">/* 递归调用请求关键帧动画 */</span></cl-cd>
<cl-cd data-idx="74">})();</cl-cd>
<cl-cd data-idx="75">&nbsp;</cl-cd>
<cl-cd data-idx="76"><span class="tGreen">/* audio 监听事件 :显示相应提示语 */</span></cl-cd>
<cl-cd data-idx="77">aud.onpause = aud.onplaying = () =&gt; canv.title = aud.paused ? <span class="tMagenta">'点击播放'</span> : <span class="tMagenta">'点击暂停'</span>;</cl-cd>
<cl-cd data-idx="78"><span class="tGreen">/* 画布点击事件 :播放或暂停音乐 */</span></cl-cd>
<cl-cd data-idx="79">canv.onclick = () =&gt; aud.paused ? aud.play() : aud.pause();</cl-cd>
<cl-cd data-idx="80">&nbsp;</cl-cd>
<cl-cd data-idx="81">&lt;<span class="tDarkRed">/script</span>&gt;</cl-cd>
<cl-cd data-idx="82">&nbsp;</cl-cd>
<cl-cd data-idx="83">&lt;<span class="tDarkRed">/body</span>&gt;</cl-cd>
<cl-cd data-idx="84">&lt;<span class="tDarkRed">/html</span>&gt;</cl-cd>
</div>
<p><br><br>上述实例,利用html5的audio元素作为音源输出,web audio api 获得相关音频实时数据后经过一系列的处理实时传给 output 数组,canvas 画布则实时将 output 数组里的音频数据转化为频谱条的高度并绘制出来,形成音频与频谱联动的响应式动态效果。代码的注解我写的已经相当详细,但要彻底理解其工作原理,需要自己网补一下 web audio api,注意它不是H5的audio元素。</p>

马黑黑 发表于 2024-3-19 12:23

PHPTS 默认不随计算机的启动而自启动,就是说,你需要时运行 phpts.exe ,虚拟服务器就启动了,但下次开机你若想继续使用 phpts,你需要重新启动它。这对资源消耗来说是有用的。

没有特别需要,只是想能够调试 HTML 网页文件,那么,一切配置都可以不做,运行好后关掉程序窗口就行。然后就可以尽情调试本地机器上的网站。使用相同家庭局域网上网的其他设备,可以通过运行了 phpts 的机器的 ip 地址访问里面的网站或网页。

PHPTS 官网:https://www.phpts.com/

起个网名好难 发表于 2024-3-19 12:30

这样配置也还是只能在本地(自己的计算机上)玩吧。

马黑黑 发表于 2024-3-19 12:31

canvas画布的宽高尺寸应在 HTML 代码中设置,不要在CSS里做。canvas画布的宽度会影响频谱条个体的宽度,频谱条的宽度,依赖画布宽度和 len 变量的数字大小。

len 变量实际上就是频谱条总数,但这个数不是随意设定的。音频振幅、高低等数据的条数,根据 fftSize 值除以2获取,len 变量可酌情减少,以便让频谱的动态变化左右都有,若每个音频数据条对应一条频谱,则右侧的频谱会有很多条整过播放过程都不会动弹或动的很少,影响整体美观。

除了使用canvas实时绘制频谱,也可以也其他HTML元素模拟频谱,画布消耗的DOM资源会少得多。

马黑黑 发表于 2024-3-19 12:32

起个网名好难 发表于 2024-3-19 12:30
这样配置也还是只能在本地(自己的计算机上)玩吧。

是的。要发布,需要有自己的 htpps 协议的空间;或可以上传HTML的空间,然后音频和文档在同一个目录,再用 iframe 发布

起个网名好难 发表于 2024-3-19 12:43

马黑黑 发表于 2024-3-19 12:32
是的。要发布,需要有自己的 htpps 协议的空间;或可以上传HTML的空间,然后音频和文档在同一个目录,再 ...

本地配置个服务器有些奢侈,就5#的这句话就够了。

马黑黑 发表于 2024-3-19 12:45

起个网名好难 发表于 2024-3-19 12:43
本地配置个服务器有些奢侈,就5#的这句话就够了。

若不配置,需要发布→修改→发布,这可能会很反复

马黑黑 发表于 2024-3-19 12:48

频谱条高度的计算,除非抓取过一次音轨并记录最高频率的数据,否则需要估算一下系数,一楼代码的 68 行:

ppHeight = output * .75 + ppWidth;

.75 是估算的系数。系数的作用是整个播放过程中最高的频谱条不突破 canvas 的高度。

起个网名好难 发表于 2024-3-19 13:55

马黑黑 发表于 2024-3-19 12:45
若不配置,需要发布→修改→发布,这可能会很反复

不用在自己的计算机上安装、配置服务器,对于普通的网友安装配置个服务器难度不是一般的大,传到一个是https的空间就行了。<br>
<iframe src="https://file.uhsea.com/2403/faf8f5521b9e689a88054282be9b5831L6.html"frameborder='0' scrolling='no'
style="width:315px;height:315px;margin:auto;position:relative;"></iframe>

起个网名好难 发表于 2024-3-19 14:03

马黑黑 发表于 2024-3-19 12:45
若不配置,需要发布→修改→发布,这可能会很反复

还有个问题,若浏览器不能自动播放则点击也不灵,能自动播放的则点击事件也能响应。

起个网名好难 发表于 2024-3-19 16:35

本帖最后由 起个网名好难 于 2024-3-19 16:46 编辑

The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page。
/* 画布点击事件 :播放或暂停音乐 */
canv.onclick = () => {
        if(Ac.state !== 'running') {
                Ac.resume();
        }
        aud.paused ? aud.play() : aud.pause();
}


马黑黑 发表于 2024-3-19 18:13

起个网名好难 发表于 2024-3-19 16:35
The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on t ...

这是自动播放的问题,除了对 audio 标签,还有音频上下文也有这个需求。若不自动不放,或浏览器设置非常理想,估计没问题。9#的我这没问题。

马黑黑 发表于 2024-3-19 18:14

起个网名好难 发表于 2024-3-19 14:03
还有个问题,若浏览器不能自动播放则点击也不灵,能自动播放的则点击事件也能响应。

对。网络不好时,音频不能俺预期load下来也不行

马黑黑 发表于 2024-3-19 18:15

起个网名好难 发表于 2024-3-19 13:55
不用在自己的计算机上安装、配置服务器,对于普通的网友安装配置个服务器难度不是一般的大,传到一个是 ...

所以我推荐的是 phpts,它是安装好了就可以直接使用了

起个网名好难 发表于 2024-3-19 18:22

马黑黑 发表于 2024-3-19 18:13
这是自动播放的问题,除了对 audio 标签,还有音频上下文也有这个需求。若不自动不放,或浏览器设置非常 ...

我这9#只有firfox行,chrome和edge不行,控制台就出现的是11#的那条信息。

马黑黑 发表于 2024-3-19 18:25

起个网名好难 发表于 2024-3-19 18:22
我这9#只有firfox行,chrome和edge不行,控制台就出现的是11#的那条信息。

这个音频上下文api还是和浏览器有一定的关系的

起个网名好难 发表于 2024-3-19 18:26

马黑黑 发表于 2024-3-19 18:14
对。网络不好时,音频不能俺预期load下来也不行

应该是不能自动播放的原因,加上resume后就解决了(本地nginx测试)

起个网名好难 发表于 2024-3-19 18:27

本帖最后由 起个网名好难 于 2024-3-19 18:33 编辑

马黑黑 发表于 2024-3-19 18:15
所以我推荐的是 phpts,它是安装好了就可以直接使用了
如果仅是测试音频频谱的话nginx更简单,完全没配置的需求。

马黑黑 发表于 2024-3-19 18:57

起个网名好难 发表于 2024-3-19 18:27
如果仅是测试音频频谱的话nginx更简单,完全没配置的需求。

这个 HTTPS 用的就是 Nginx

起个网名好难 发表于 2024-3-19 19:13

马黑黑 发表于 2024-3-19 18:57
这个 HTTPS 用的就是 Nginx

本地测试无所谓http 或 https, 不另外配置就是http。

https 是这论坛的要求,要上传并在论坛里展示就需要找个支持https的空间了。

页: [1] 2
查看完整版本: 实现本地可视化音频播放效果实例