实现本地可视化音频播放效果实例
<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"><!DOCTYPE html></cl-cd>
<cl-cd data-idx="2"><<span class="tDarkRed">html</span> lang=<span class="tMagenta">"zh"</span>></cl-cd>
<cl-cd data-idx="3"><<span class="tDarkRed">head</span>></cl-cd>
<cl-cd data-idx="4"><<span class="tDarkRed">meta</span> charset=<span class="tMagenta">"utf-8"</span> /></cl-cd>
<cl-cd data-idx="5"><<span class="tDarkRed">title</span>>web audio API<<span class="tDarkRed">/title</span>></cl-cd>
<cl-cd data-idx="6"><<span class="tDarkRed">style</span>></cl-cd>
<cl-cd data-idx="7"> #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"> #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"><<span class="tDarkRed">/style</span>></cl-cd>
<cl-cd data-idx="10"><<span class="tDarkRed">/head</span>></cl-cd>
<cl-cd data-idx="11"><<span class="tDarkRed">body</span>></cl-cd>
<cl-cd data-idx="12"> </cl-cd>
<cl-cd data-idx="13"><<span class="tDarkRed">div</span> <span class="tRed">id</span>=<span class="tMagenta">"papa"</span>></cl-cd>
<cl-cd data-idx="14"> <<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>><<span class="tDarkRed">/canvas</span>></cl-cd>
<cl-cd data-idx="15"><<span class="tDarkRed">/div</span>></cl-cd>
<cl-cd data-idx="16"> </cl-cd>
<cl-cd data-idx="17"><<span class="tDarkRed">script</span>></cl-cd>
<cl-cd data-idx="18"> </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"> </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"> </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"> </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"> canvctx.clearRect(0, 0, canv.width, canv.height);</cl-cd>
<cl-cd data-idx="63"> <span class="tGreen">/* 分析器获取实时音频频率数据 :这里是 0 ~ 255 Uint8Array 并赋值给 output 数组 */</span></cl-cd>
<cl-cd data-idx="64"> analyser.getByteFrequencyData(output);</cl-cd>
<cl-cd data-idx="65"> x = 0.5; <span class="tGreen">/* 留一点点边 */</span></cl-cd>
<cl-cd data-idx="66"> <span class="tGreen">/* 绘制 len 条柱状频谱 */</span></cl-cd>
<cl-cd data-idx="67"> <span class="tBlue">for</span>(<span class="tBlue">let</span> i = 0; i < len; i ++) {</cl-cd>
<cl-cd data-idx="68"> ppHeight = output * .75 + ppWidth;</cl-cd>
<cl-cd data-idx="69"> canvctx.fillStyle = gradient;</cl-cd>
<cl-cd data-idx="70"> canvctx.fillRect(x, canv.height - ppHeight, ppWidth - 1, ppHeight);</cl-cd>
<cl-cd data-idx="71"> x += ppWidth;</cl-cd>
<cl-cd data-idx="72"> }</cl-cd>
<cl-cd data-idx="73"> requestAnimationFrame(draw); <span class="tGreen">/* 递归调用请求关键帧动画 */</span></cl-cd>
<cl-cd data-idx="74">})();</cl-cd>
<cl-cd data-idx="75"> </cl-cd>
<cl-cd data-idx="76"><span class="tGreen">/* audio 监听事件 :显示相应提示语 */</span></cl-cd>
<cl-cd data-idx="77">aud.onpause = aud.onplaying = () => 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 = () => aud.paused ? aud.play() : aud.pause();</cl-cd>
<cl-cd data-idx="80"> </cl-cd>
<cl-cd data-idx="81"><<span class="tDarkRed">/script</span>></cl-cd>
<cl-cd data-idx="82"> </cl-cd>
<cl-cd data-idx="83"><<span class="tDarkRed">/body</span>></cl-cd>
<cl-cd data-idx="84"><<span class="tDarkRed">/html</span>></cl-cd>
</div>
<p><br><br>上述实例,利用html5的audio元素作为音源输出,web audio api 获得相关音频实时数据后经过一系列的处理实时传给 output 数组,canvas 画布则实时将 output 数组里的音频数据转化为频谱条的高度并绘制出来,形成音频与频谱联动的响应式动态效果。代码的注解我写的已经相当详细,但要彻底理解其工作原理,需要自己网补一下 web audio api,注意它不是H5的audio元素。</p> PHPTS 默认不随计算机的启动而自启动,就是说,你需要时运行 phpts.exe ,虚拟服务器就启动了,但下次开机你若想继续使用 phpts,你需要重新启动它。这对资源消耗来说是有用的。
没有特别需要,只是想能够调试 HTML 网页文件,那么,一切配置都可以不做,运行好后关掉程序窗口就行。然后就可以尽情调试本地机器上的网站。使用相同家庭局域网上网的其他设备,可以通过运行了 phpts 的机器的 ip 地址访问里面的网站或网页。
PHPTS 官网:https://www.phpts.com/ 这样配置也还是只能在本地(自己的计算机上)玩吧。 canvas画布的宽高尺寸应在 HTML 代码中设置,不要在CSS里做。canvas画布的宽度会影响频谱条个体的宽度,频谱条的宽度,依赖画布宽度和 len 变量的数字大小。
len 变量实际上就是频谱条总数,但这个数不是随意设定的。音频振幅、高低等数据的条数,根据 fftSize 值除以2获取,len 变量可酌情减少,以便让频谱的动态变化左右都有,若每个音频数据条对应一条频谱,则右侧的频谱会有很多条整过播放过程都不会动弹或动的很少,影响整体美观。
除了使用canvas实时绘制频谱,也可以也其他HTML元素模拟频谱,画布消耗的DOM资源会少得多。 起个网名好难 发表于 2024-3-19 12:30
这样配置也还是只能在本地(自己的计算机上)玩吧。
是的。要发布,需要有自己的 htpps 协议的空间;或可以上传HTML的空间,然后音频和文档在同一个目录,再用 iframe 发布 马黑黑 发表于 2024-3-19 12:32
是的。要发布,需要有自己的 htpps 协议的空间;或可以上传HTML的空间,然后音频和文档在同一个目录,再 ...
本地配置个服务器有些奢侈,就5#的这句话就够了。 起个网名好难 发表于 2024-3-19 12:43
本地配置个服务器有些奢侈,就5#的这句话就够了。
若不配置,需要发布→修改→发布,这可能会很反复 频谱条高度的计算,除非抓取过一次音轨并记录最高频率的数据,否则需要估算一下系数,一楼代码的 68 行:
ppHeight = output * .75 + ppWidth;
.75 是估算的系数。系数的作用是整个播放过程中最高的频谱条不突破 canvas 的高度。 马黑黑 发表于 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 12:45
若不配置,需要发布→修改→发布,这可能会很反复
还有个问题,若浏览器不能自动播放则点击也不灵,能自动播放的则点击事件也能响应。 本帖最后由 起个网名好难 于 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 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 14:03
还有个问题,若浏览器不能自动播放则点击也不灵,能自动播放的则点击事件也能响应。
对。网络不好时,音频不能俺预期load下来也不行 起个网名好难 发表于 2024-3-19 13:55
不用在自己的计算机上安装、配置服务器,对于普通的网友安装配置个服务器难度不是一般的大,传到一个是 ...
所以我推荐的是 phpts,它是安装好了就可以直接使用了 马黑黑 发表于 2024-3-19 18:13
这是自动播放的问题,除了对 audio 标签,还有音频上下文也有这个需求。若不自动不放,或浏览器设置非常 ...
我这9#只有firfox行,chrome和edge不行,控制台就出现的是11#的那条信息。 起个网名好难 发表于 2024-3-19 18:22
我这9#只有firfox行,chrome和edge不行,控制台就出现的是11#的那条信息。
这个音频上下文api还是和浏览器有一定的关系的 马黑黑 发表于 2024-3-19 18:14
对。网络不好时,音频不能俺预期load下来也不行
应该是不能自动播放的原因,加上resume后就解决了(本地nginx测试) 本帖最后由 起个网名好难 于 2024-3-19 18:33 编辑
马黑黑 发表于 2024-3-19 18:15
所以我推荐的是 phpts,它是安装好了就可以直接使用了
如果仅是测试音频频谱的话nginx更简单,完全没配置的需求。
起个网名好难 发表于 2024-3-19 18:27
如果仅是测试音频频谱的话nginx更简单,完全没配置的需求。
这个 HTTPS 用的就是 Nginx 马黑黑 发表于 2024-3-19 18:57
这个 HTTPS 用的就是 Nginx
本地测试无所谓http 或 https, 不另外配置就是http。
https 是这论坛的要求,要上传并在论坛里展示就需要找个支持https的空间了。
页:
[1]
2