处理audio控件能否自动播放的另一种机制
本帖最后由 马黑黑 于 2024-2-26 21:28 编辑 <br /><br /><style>.mama { font-size: 18px; }
.mama p { margin: 10px 0; }
.mama mark { padding: 2px 6px; background: lightblue; }
.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: thin solid 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>
<div class="mama">
<p>不久前我们在音频播放器系列开发教程里接触过有关audio音频控件的自动播放问题,我们使用<mark>autoplay 属性 + audioElement.paused</mark>来解决问题,事实证明这是可行的:<mark>audioElement.paused</mark>能让我们获得audio控件是否处于暂停状态,再通过对audio控件的其它几个事件进行监听,从而构造出几近完美的音频与音画的动态联动。autoplay的缺陷是,它常会被浏览器的限制策略所阻止,且不会报错,浏览器在控制台一般也不会有提示(个别浏览器可能产生非错误级别的友好提示)。autoplay与限制策略间的此等关系对页面设计者而言多少是一种尴尬,所以,应该设计另外一种监听机制,以帮助设计者了解更多的audio控件在开始运行时的状况。</p>
<p>其实,除了依据paused状态,确实还存在另外的应对限制策略的机制,那就是Promise API,一个现代浏览器普遍支持的基于限制策略的接口。与对待 autoplay 属性不同,现代浏览器在audio控件首次发起播放即<mark>audioElement.play()</mark>动作的时候会触发Promise对此动作做出策略响应,且能返回错误代码,我们还可以从中获取到错误的名称——浏览器禁止自动播放时该错误名称为NotAllowedError。依此Promise机制,我们可以获知浏览器允许或禁止自动播放,进而做相应处理。</p>
<p>以下代码,我们不考虑旧浏览器对Promise是否支持,仅针对现代浏览器:</p>
<div class="mum" id="mum">
<cl-cd data-idx="1"><div <span class="tRed">id</span>=<span class="tMagenta">"mydiv"</span>></cl-cd>
<cl-cd data-idx="2"> <span class="tGreen"><!-- audio不设置autoplay --></span></cl-cd>
<cl-cd data-idx="3"> <audio <span class="tRed">id</span>=<span class="tMagenta">"aud"</span> src=<span class="tMagenta">"https://music.163.com/song/media/outer/url?<span class="tRed">id</span>=408532724"</span> loop></audio></cl-cd>
<cl-cd data-idx="4"></div></cl-cd>
<cl-cd data-idx="5"><br></cl-cd>
<cl-cd data-idx="6"><script></cl-cd>
<cl-cd data-idx="7"><span class="tBlue">let</span> autoplayPromise = aud.play(); <span class="tGreen">// 命令audio播放并获取Promise对象</span></cl-cd>
<cl-cd data-idx="8"><br></cl-cd>
<cl-cd data-idx="9">autoplayPromise.then(() => {</cl-cd>
<cl-cd data-idx="10"> console.log(<span class="tMagenta">'ok'</span>); <span class="tGreen">//正常播放, 打印ok</span></cl-cd>
<cl-cd data-idx="11"><span class="tGreen">//俘获错误</span></cl-cd>
<cl-cd data-idx="12">}).<span class="tBlue">catch</span>(err => {</cl-cd>
<cl-cd data-idx="13"> console.log(err.name); <span class="tGreen">// → NotAllowedError</span></cl-cd>
<cl-cd data-idx="14"> aud.controls = true; <span class="tGreen">//令audio界面显示出来让用户手动操作</span></cl-cd>
<cl-cd data-idx="15">});</cl-cd>
<cl-cd data-idx="16"><br></cl-cd>
<cl-cd data-idx="17"><span class="tGreen">//移除audio界面</span></cl-cd>
<cl-cd data-idx="18">aud.onmouseout = () => aud.controls = false;</cl-cd>
<cl-cd data-idx="19"></script></cl-cd>
</div>
<p><button id="btnShow" type="button" value="show">运行代码</button></p>
<div id="showBox"></div>
<p>上述代码,我们使用链式方法处理 autoplayPromise 对象实例(autoplayPromise是我们自定义的变量名),该对象如前已述,它是一个 Promise 实例,因为 audioElement.play 会返回一个 Promise 对象副本。autoplayPromise.then 意为,如若 autoplayPromise 正常,那么(then就是那么的意思),做点什么,示例中是在控制台打印OK,然后 .catch 是俘获错误信息,我们打印出错误的名称,接着呈现audio界面。最后,用户不论手动播放音频与否,鼠标指针从播放器界面上移开后,我们让播放器界面消失。这个示例仅是演示,若使用于生产环境,需要做一些额外工作,以提升页面与用户交互的友好性。</p>
<p>有意思的是,在本帖运行示例代码,播放器都能成功播放,不论浏览器限制策略是否禁止自动播放。这是为什么呢?原因很简单,我们点击了“运行代码”按钮才去运行 aud.play(),浏览器就认为这是用户与页面已经做了交互、是用户许可播放音频的,aud.play 触发的 Promise 限制策略故此放行。若示例代码单独保存为一个HTML文档,情形则不一样:如果不允许自动播放,则 Promise 不会放行,演示的代码就会显示出audio界面;反之则自动播放音乐。</p>
<p>更有意思的是,当audio控件处于静音状态(muted)或音量为0(volume=0)时,限制策略也会放行audio的音频自动播放,一些实现音频自动播放的方法基于此特性,然后会引导用户解除静音状态或调解音量。这方法有点另类,且不是十分牢靠:严格机制下的限制策略,静音与音量为零是音频一样不可以自动播放,一切依赖于当前浏览器的限制级别与用户对浏览器的相关设置。所以还是不要痴心妄想,老老实实采取理性的做法:允许自动播放就自动播放,不允许就将选择权交给用户。</p>
</div>
<script>
btnShow.onclick = function() {
if(this.value === 'show') {
runCode(mum.innerText, showBox);
this.value = 'shut';
this.textContent = '关闭运行';
}else{
showBox.innerHTML = '';
this.value = 'show';
this.textContent = '运行代码';
}
};
function runCode(str,ele) {
var reg = /(<script(.*?)>)(.|\n)*?(<\/script>)/g;
var js_str, html_str;
if(str.match(reg) !== null) {
js_str = str.match(reg);
html_str = str.replace(js_str, '').trim();
js_str = js_str.replace(/<[\/]{0,1}script[^>]*>/g,'').trim();
} else {
js_str = '';
html_str = str.trim();
}
ele.innerHTML = html_str;
var myfunc = new Function(js_str);
myfunc();
};
</script>
“autoplay的缺陷是,它常会被浏览器的限制策略所阻止,且不会报错,浏览器在控制台一般也不会有提示”
有些电脑会碰到这样的问题,有些不会。已经感受到了{:4_173:} 黑黑的研究真细致,设计方方面面呢,厉害{:4_199:} 坚持把瓜吃完,看冬小雨来不{:4_203:} 这个十分智能
若示例代码单独保存为一个HTML文档,情形则不一样:如果不允许自动播放,则 Promise 不会放行,演示的代码就会显示出audio界面;反之则自动播放音乐。
试了一下。。。好玩得很{:4_173:} 南无月 发表于 2024-2-26 18:23
这个十分智能
若示例代码单独保存为一个HTML文档,情形则不一样:如果不允许自动播放,则 Promise 不会放 ...
谢玩 樵歌 发表于 2024-2-26 15:45
坚持把瓜吃完,看冬小雨来不
瓜皮都不剩了,来有啥用{:4_170:} 红影 发表于 2024-2-26 14:45
“autoplay的缺陷是,它常会被浏览器的限制策略所阻止,且不会报错,浏览器在控制台一般也不会有提示”
...
{:4_181:} 红影 发表于 2024-2-26 14:46
黑黑的研究真细致,设计方方面面呢,厉害
没有没有,还是很粗糙 马黑黑 发表于 2024-2-26 19:37
现在能给出失败原因,真的很棒的{:4_187:} 马黑黑 发表于 2024-2-26 19:37
没有没有,还是很粗糙
黑黑谦虚,对这个之前一点都不知道呢,学习了{:4_187:} 红影 发表于 2024-2-26 19:41
黑黑谦虚,对这个之前一点都不知道呢,学习了
我这个处理确实是很偷懒的。你看网易云音乐播放器,它的代码那才叫多,多到你没有办法阅读,当然,它解决了所有限制实现了自动播放,目前没有几个大厂能做的那么好。 红影 发表于 2024-2-26 19:41
现在能给出失败原因,真的很棒的
这个可以给,不用promise也可以给 马黑黑 发表于 2024-2-26 19:36
谢玩
{:4_170:}歌是真的好听。。。 马黑黑 发表于 2024-2-26 19:43
我这个处理确实是很偷懒的。你看网易云音乐播放器,它的代码那才叫多,多到你没有办法阅读,当然,它解决 ...
对那个不懂啊,从来没去看过呢{:4_173:} 马黑黑 发表于 2024-2-26 19:44
这个可以给,不用promise也可以给
问题是怎么给一个都不知道呢{:4_173:} 红影 发表于 2024-2-26 20:11
问题是怎么给一个都不知道呢
那个保姆级教程不是说了吗 红影 发表于 2024-2-26 20:10
对那个不懂啊,从来没去看过呢
听过,偷过,就从来没想过人家总是可以自动播放的{:4_170:} 南无月 发表于 2024-2-26 19:52
歌是真的好听。。。
不错的,这是德国一名音乐家的歌曲,1956年出生,从小就对音乐有特殊的爱好和理解,所创造的曲子都是经典的 马黑黑 发表于 2024-2-26 19:36
瓜皮都不剩了,来有啥用
下次给他留下一小片{:4_189:}