TGideas:SoundJs | Web-Audio-API实现多音频播放
因为HTML5 Audio不支持同时播放多首歌,今天我们主要介绍一下soundjs和Web-Audio-API实现多音频播放的方法,文末还总结了两种实现方式的demo,大家可以依据实际项目情况添加接口。
| 导语 再回首-“凤凰传奇邀你唱H5”
前言
需求背景:用户选择玲花或曾毅,参与合唱,合唱成功后上传用户录音数据,封装待播放的音频信息(这些部分都是由林雨哥[lennylin]完成的),到落版页请求歌曲进行播放。其中ios微信侧录制音频会屏蔽掉媒体本身的声音,所以ios播放用户合唱歌曲需要同时播放用户录制音频和背景音乐。
(凤凰传奇邀你唱H5)
目录
1. soundjs多音频播放
常用属性:
duration |
音频时长 |
volume |
音量 |
playState |
音频播放状态:playFinished | playSucceeded |
paused |
s.paused = true 设置音频暂停 |
startTime |
音频开始播放时间点 |
loop |
音频循环次数 |
常用方法:
play() | 音频播放 |
stop() | 音频暂停播放,重置播放位置为0,如果想保留播放位,可以使用sound.paused = true |
常用事件:
complete | 音频播放完成 |
succeeded | 回放成功时触发 |
首先我们看下单个音频播放:
createjs.Sound.alternateExtensions //设置声音后缀名
createjs.Sound.registerSound //注册音频,sound播放音乐之前需先注册
createjs.Sound.on("fileload", this.loadHandler, this); //针对每个注册音频加载完后的处理事件
createjs.Sound.alternateExtensions = ["mp3"];
createjs.Sound.on("fileload", this.loadHandler, this);
createjs.Sound.registerSound("path/to/mySound.ogg", "sound");
function loadHandler(event) {
var instance = createjs.Sound.play("sound"); // 可使用ID、完整的源路径或event.src。
instance.on("complete", this.handleComplete, this);
instance.volume = 0.5;
}
多音频播放:
sounds.forEach(function (sound) {
createjs.Sound.alternateExtensions = ["mp3"];
createjs.Sound.registerSound(sound.src, sound.id);
var promise = new Promise(function (resolve, reject) {
createjs.Sound.on("fileload", function (event) {
if (sound.id == event.id) {
resolve(createjs.Sound.play(event.id))
}
})
})
promises.push(promise)
})
return Promise.all(promises)
值得注意的是,当sound.src是一个会重定向的音频地址,需要先获得重定向后的真实地址,再注册音频。回顾我们的需求,落版页听录制歌曲的时候,我们需要拿用户openid去请求服务器端的php文件,服务器会重定向到一个具体的mp3地址,代码片段如下:
createjs.Sound.registerSound('xxx.php?openid=xxx', 'userMp3'); ×
createjs.Sound.registerSound(request.responseURL, 'userMp3'); √
先看一下readyState几种状态
0 |
|
|
1 |
|
|
2 |
HEADERS_RECEIVED (已获取响应头) |
|
3 |
LOADING (正在下载响应体) |
响应体下载中 |
4 |
DONE (请求完成) |
整个请求过程已经完毕. |
当状态为HEADERS_RECEIVED时可以拿到多次重定向后的最终 URL 并终止请求
if
(this.readyState == this.HEADERS_RECEIVED) {
createjs.Sound.registerSound(request.responseURL, sound.id);
this.abort();
}
注意:如果在初始化音频的时候再去发request请求,会导致微信侧播放异常,所以提前获取音频真实地址,在回调里面再执行音频初始化。
当点击恢复音频播放时需要考虑是否所有音频都已经播放完成,如果是则调用play(),否则仅仅设置paused的状态为false。因为这个时候短音频可能已经播放完成,长音频未播放完成并处于暂停状态,如果不判断直接调用play(),短音频会重新开始播放。
playAll(){
let allFinished = false;
this.sounds.forEach(function (sound) {
allFinished = sound.playState !== 'playFinished' ? false : true;
});
this.sounds.forEach(function(sound){
if(allFinished){
sound.play();
}else{
sound.paused = false;
}
});
}
2. Web-Audio-API多音频播放
AudioContext |
音频上下文 控制其包含节点的创建、处理和解码,使用其他接口之前需创建一个音频上下文 |
AudioNode | 音频节点 |
AudioBuffer |
音频数据对象 可以通过AudioContext.createBuffer 来创建或者 通过 AudioContext.decodeAudioData成功解码音轨后获取. |
AudioBufferSourceNode |
方法:AudioBufferSourceNode.start() AudioBufferSourceNode.stop() 事件:ended |
ended |
音频播放停止时触发 |
2.2 创建单音频播放
function
playSound(buffer){
var context = new (window.AudioContext || window.webkitAudioContext)(),
source = context.createBufferSource();
source.buffer = buffer;// 告诉音频源 播放哪一段音频
source.connect(context.destination);// 连接到输出源
source.start(0);//开始播放
}
定义音频上下文需注意:webkit内核的浏览器需要带webkit前缀(webkitAudioContext)
2.3 多音频播
opts = [{ id: '1mp3', url: 'mp3/select_1_1.mp3' },{ id: '2mp3', url: 'mp3/select_0_1.mp3' } ]
opts.forEach(opt => {
var request = new XMLHttpRequest();
var context = new (window.AudioContext || window.webkitAudioContext)();
self.sounds[opt.id] = new Sound(opt.id, opt.url, context);
request.open('GET', opt.url, true);
request.responseType = 'arraybuffer';
var p = new Promise(function (resolve, reject) {
//下面就是对音频文件的异步解析
request.onload = function () {
context.decodeAudioData(request.response, function (buffer) {
self.sounds[opt.id].setBuffer(buffer);
self.sounds[opt.id].setEnd(0);
resolve(self.sounds[opt.id]);
}, function (err) {
reject(err);
});
};
});
request.send();
promises.push(p);
});
一个 AudioBufferSourceNode 只能被播放一次,每次调用 start() 之后,如果还想再播放一遍同样的声音,那么就需要再创建一个 AudioBufferSourceNode,如果想要多次播放声音,需要保留音频播放上下文和音频数据,这里可以定义一个class,初始化每个音频的id,url地址,buffer数据以及音频上下文等等。
class
Sound {
//音频id,音频url,音频播放上下文
constructor(id, url, context) {
this.id = id;
this.url = url;
this.context = context;
this.endState = 0;
}
//设置音频数据
setBuffer(buf) {
this.buffer = buf;
}
//定义音频播放完成事件
setEnd(end) {
this.endState = end;
}
on(event, callback) {
this.events[event] = callback;
}
//音频播放事件
play() {
}
//音频暂停事件
pause() {
}
}
2.4 音频暂停、播放
if ( context.state === 'running') {
context.suspend().then(function () {
console.log('Resume context');
});
} else if (context.state === 'suspended') {
context.resume().then(function () {
console.log('Suspend context');
});
}
2.5 音频播放结束
绑定音频播放结束事件
sound.on('ended', function () {
sound.setEnd(1);
})
综上所述,当对音频点击操作时,需要先判断音频播放状态,如果正在播放则直接暂停;如果已经暂停并且没有播放结束,那么执行恢复操作resume();如果音频已经播放完成,则
需要再创建一个 AudioBufferSourceNode,重新绑定context、buffer等信息
if(this.context.state === 'running' && this.endState === 0){
this.context.suspend();
}
else if (this.context.state === 'suspended' && this.endState === 0) {
this.context.resume();
}
else{
this.source = this.context.createBufferSource();
if (this.events.ended) {
this.source.onended = this.events.ended;
}
this.source.buffer = this.buffer;// 告诉音频源 播放哪一段音频
this.source.connect(this.context.destination);// 连接到输出源
this.source.start(0);//开始播放
this.endState = 0;
}
3. 音频播放进度条实现
*idx / 100
。
function draw() {
if (idx < 101) {
inside.attr("stroke-dasharray", "" + circleLength * idx / 100 + ",10000");
idx++;
} else {
if (inter)
clearInterval(inter);
inter = null;
idx = 1;
btnEnd.show();
btnStop.hide();
btnStart.hide();
}
}
值得注意的是,如果是多首歌曲同时播放,需要考虑歌曲的最大时长来设置进度条速度,代码片段如下:
allSound.forEach(function (sound, index) {
durations.push(sound.duration);
if (sound.duration > duration) {
duration = sound.duration;
i = index;
}
});
space = Math.max(...durations) / 100;
总结
1. soundjs实现的音频播放
调用方法:
var mSrc = [{
id:"firstMp3",src:"//game.gtimg.cn/images/hx/act/a20180305song/assets/select_1_1.mp3"},{id:"secondMp3",src:"//game.gtimg.cn/images/hx/act/a20180305song/assets/select_0_1.mp3"
}];
playSound.init(mSrc);//初始化
$('.pop_disk').on("click", function () {
if (playSound.sound.longest.playState !== 'playFinished' && playSound.sound.longest.paused == false) {
playSound.stopDraw();
playSound.sound.pauseAll();
btnStart.hide();
btnStop.show();
} else {
playSound.startDraw();
playSound.sound.playAll();
btnStart.show();
btnStop.hide();
}
btnEnd.hide();
})
2. Web-Audio-API实现的音频播放
http://hx.qq.com/act/a20180305song/test.html
(AudioContext)
Web-Audio-API调用方法:
var index = 0,idValue = 0, duration = 0, durations = [];
AwesomeSound.load([
{ id: '1mp3', url: '//game.gtimg.cn/images/hx/act/a20180305song/assets/select_1_1.mp3' },
{ id: '2mp3', url: '//game.gtimg.cn/images/hx/act/a20180305song/assets/select_0_1.mp3' }
]).then(function (sounds) {
sounds.forEach(function (sound, i) {
durations.push(sound.buffer.duration);
if (sound.buffer.duration > duration) {
duration = sound.buffer.duration;
index = i;
idValue = sound.id;
}
sounds[index].on('ended', function () {
sound.setEnd(1);
playmp3.html("点击再次播放");
})
sound.play();
});
});
playmp3.click(function () {
var playState = AwesomeSound.getState(idValue);
if (playState === 'running') {
playmp3.html("暂停播放");
}
else {
playmp3.html("正在播放");
}
AwesomeSound.playALL();
});
- (转自TGideas的SoundJs | Web-Audio-API实现多音频播放,技术研究https://tgideas.qq.com/gicp/news/475/6594222.html?from=list) 链接是:
标签
关注微信号:h5-share,获取更多创意H5案例分享!也可访问H5案例分享网站www.h5anli.com,搜索查阅~
微信扫一扫
关注该公众号