基于Howler.js 解决音频音效连续播放失声问题

您所在的位置:网站首页 js背景音乐连续播放 基于Howler.js 解决音频音效连续播放失声问题

基于Howler.js 解决音频音效连续播放失声问题

2024-04-11 03:28| 来源: 网络整理| 查看: 265

howler.js是一款非常强大的声音引擎,功能强大,简单使用,性能良好,并且支持多种声音格式(如MP3、WEBM、MPEG、MP4等),多应用在各个项目中。

背景

近期协助开发了一个移动端游戏页面,需要增加音频以及音效,需求如下:

用户进入首页需要播放音乐,当用户没有关闭音乐,首页音乐要循环播放;

用户在碰撞到部分物品产生碰撞音效,且碰撞一次音效就需要播放一次。

从技术选型上自然而然选择了howler作为音频音效的主力引擎,应用到页面开发。

首页音乐循环播放的解决方案

参考官网示例,代码如下:

import {Howl} from 'howler'; var sound = new Howl({ src: ['sound.mp3'], loop: true, html5: true, }) // 寻找时机触发播放 sound.play();

通过简单配置就可实现音乐循环播放。但是在实际测试过程中,却发现有部分机型在无任何用户操作行为下,播放音乐一段时间后,声音会莫名消失,再间隔一段时间后,音乐又会继续播放。

出现这个问题,第一时间思考的是

声音为何会消失? 以什么样的形式消失(播放失败、静音、暂停、音量减少了)? 为何会间隔一段时间才会继续播放?

针对以上问题,我就开始查找相关文档,在该段音频上注册了n个监听事件后,等待音乐循环播放的时机,以期待能够发现真正的问题原因是什么。

import {Howl} from 'howler'; var sound = new Howl({ src: ['sound.mp3'], loop: true, html5: true, }) sound.play(); // 播放监听事件 sound.on('play', function () { console.log('音乐播放了'); }) // 播放失败监听 sound.on('playerror', function () { console.log('音乐播放失败'); }) // 静音监听 sound.on('mute', function () { console.log('音乐静音了'); }) // 暂停监听 sound.on('pause', function () { console.log('音乐暂停了'); }) // 音量改变监听 sound.on('volume', function() { console.log('音乐的音量改变了') }) // 音频结束监听 sound.on('end', function () { console.log('音乐播放停止') })

自测过程中,我发现页面播放音乐过程中,先打印了音乐播放停止后,后重新打印了音乐播放了.随后重复触发了play和end的事件,但是其他事件均未触发。且问题机型音乐多半会一次有声音一次无声音,时间大概是音频的一次播放时间,每次有声的音频还都可以重新正常播放。

为了获取到具体音频时间,查看了下API,我在play的事件里面增加了对音频播放时长的获取。

sound.on('play', function (id) { console.log('音乐播放了',id); const duration = sound.duration(id); console.log(duration, 'duration'); })

继续自测,结论依然如此,没发现其他现象。我之前在使用howler.js时,就发现使用howler有一个很头疼的问题:

一段音乐正在播放,如果重复触发音乐的播放,音乐会有一定机率出现无声。

针对无声的解决方案,查询了一下网上结论,如:

若想重复触发音乐,且完美实现音乐无异常播放,建议先将先前的音乐实例销毁再重新创建继而播放,这样在播放的时候就可以正常放声了。

但是这里需要注意,音乐实例销毁是会直接打断当前音乐的播放进程的。如果音乐播放到一定阶段(没有完全播放完毕),销毁实例再重新创建播放,一段不完整的音乐就会立马出现,且如果音乐很短时,甚至会出现所谓的“杂音”,所以音乐的播放进度也是一个非常重要的关切点,毕竟没有人愿意听一段不完美的音乐。

简单查看上述代码,在创建音乐实例的时候,仅有loop属性是涉及音乐循环的,自然就成为“怀疑对象”。所以就想看看howler是如何实现音频的循环播放的。

loop: function() { var self = this; var args = arguments; var loop, id, sound; // Determine the values for loop and id. if (args.length === 0) { // Return the grou's loop value. return self._loop; } else if (args.length === 1) { if (typeof args[0] === 'boolean') { loop = args[0]; self._loop = loop; } else { // Return this sound's loop value. sound = self._soundById(parseInt(args[0], 10)); return sound ? sound._loop : false; } } else if (args.length === 2) { loop = args[0]; id = parseInt(args[1], 10); } // If no id is passed, get all ID's to be looped. var ids = self._getSoundIds(id); for (var i=0; i { sound = new Howl({ src: ['sound.mp3'], // loop: true, html5: true, }) sound.play(); }) })

就这样通过监听音乐的播放结束事件,在结束后,销毁实例并重新创建,调用播放方法,解决了首页音乐的失声问题。

撞击音频的解决方案

刚才提到了一个问题,那就是如果当前音频正在播放,一旦重复触发该音频的声音播放,那么当前的音频大概率是无声的。所以如果在玩游戏过程中,多次碰撞产生音效,无声这个问题还是绕不开。

按照上面代码思路,直接使用销毁再重新创建的方法,因为高频率触发事件,可能音乐没等播放完,实例就销毁了,反复多次,不仅音乐无法正常播放,还会出现“群音乱舞”。所以,为了尽可能让音乐正常播放完毕,尽可能避免播放一半就销毁的场景,我想到的方案就是:

先将音频的时间缩短,让音频在还没触发前尽可能播放完,触发频率能控制在音频时间以内最好,这样不妨碍音频的正常播放。

判断当前音乐是否播放,减少高频触发条件。如果没播放,音频播放。如果音频播放着,就不作处理了。从侧面减少触发次数,不去打断音频的播放,让音频一次播放完。

在音频播放完毕后,销毁掉实例,待下次播放时,再手动创建实例。

import {Howl} from 'howler'; var sound = new Howl({ src: ['sound.mp3'], // 1. 缩短相应的音频时长 html5: true, }); // 2.判断当前音频是否播放 const isPlaying = sound.isPlaying; if(!isPlaying) { sound.play(); } sound.on('end', function () { // 3. 销毁实例 sound.unload(); sound = null; })

看不出一点毛病,哈哈哈,感觉离“成功”又进了一步。部署上线后,重新测试,发现依然会有这个问题:短时间内碰撞产生第一段音频,第一段音频看似播放完了,在相隔很短时间再次碰撞触发音频,音频还是会“失声”。这个“失声”貌似真的无法绕开了!

这时候,测试过程中突然发现一个业务场景:同时触发两段不同音频,音频是不会干扰的!

import {Howl} from 'howler'; // 第一段音频 var sound = new Howl({ src: ['sound-a.mp3'], html5: true, }); // 第二段音频 var sound1 = new Howl({ src: ['sound-b.mp3'], html5: true, }); // 同时播放两段不同的音频,音频播放正常 sound.play(); sound1.play();

既然两段不同的音频同时触发没毛病,那么相同的音频又该如何触发呢?

首先创建两个变量,接入相同的音频。

然后判断第一段音频没有播放,就播放第一段音频;如果第一段音频没有播放,那么播放第二段音频。

同时,为了防止失声的情况,还将销毁创建逻辑一并加上了。

import {Howl} from 'howler'; // 第一段音频 var sound = new Howl({ src: ['sound.mp3'], html5: true, }); // 第二段音频 var sound1 = new Howl({ src: ['sound.mp3'], html5: true, }); // 判断第一段音频是否播放 const isPlaying = sound.isPlaying; if(!isPlaying) { sound.play(); } else { // 判断当前第二段音频是否播放 const isPlaying = sound1.isPlaying; if(!isPlaying) { sound1.play(); } } sound.on('end', function () { console.log('sound播放结束'); // 销毁实例 sound.unload(); sound = null; }) sound1.on('end', function () { console.log('sound1播放结束'); // 销毁实例 sound1.unload(); sound1 = null; })

简单跑了一下,音频暂时播放正常,随后就部署上线测试了。结果没过一会儿,测试反馈: 在玩了几次游戏后,页面会很卡,游戏体验降至“最差“。

反复捉摸上面的代码,音频时间、音频状态判断以及销毁都做了,不仅没有改好原来的问题,还让游戏崩溃了。排查卡顿原因可能是高频碰撞生成音频,不断创建了新的变量,变量不能及时清空导致内存不断增加,页面卡顿。既然不能一直销毁重建,那就暂时先将音频销毁的流程取消试试,看看后续结果,自己也是听天由命了,默默做好了“投降”的准备。

import {Howl} from 'howler'; // 第一段音频 var sound = new Howl({ src: ['sound.mp3'], html5: true, }); // 第二段音频 var sound1 = new Howl({ src: ['sound.mp3'], html5: true, }); // 判断第一段音频是否播放 const isPlaying = sound.isPlaying; if(!isPlaying) { sound.play(); } else { // 判断当前第二段音频是否播放 const isPlaying = sound1.isPlaying; if(!isPlaying) { sound1.play(); } }

就这样,两段相同的音频没有重复创建销毁,只是在循环播放。触发播放方法时,优先播放第一段音频,如果第一段音频播放着,那就播放第二段音频。两段音频交替播放,有着足够的时间去缓冲。

亲自测试后,卡顿的问题不再发生了,同时在大部分情况下,音频也无失声的风险。卡顿现象消失了,音乐正常播放了,暂时无复现失声的情况,同时满足了产品的需求了,自己也深深的叹了一口气。

自此,解决howler连续播放失声的问题,有了三个参考方向:

在保证音频音质的情况下减少音频的时长;

低频触发音频的时候可以考虑创建销毁再播放;

高频触发播放使用双音频交替方式。

本次梳理也算是告一段落,真的很庆幸自己一直在很努力尝试的解决这个”失声“的问题。

题外话

第一次写问题分享,写的不好,望大佬们批评指正。如果有额外的经验,能够保证音频在高频触发时正常播放,可以交流下,感恩~~~~!



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3