TGideas:SoundJs | Web-Audio-API实现多音频播放

2019-03-25 H5案例分享 H5案例分享

因为HTML5 Audio不支持同时播放多首歌,今天我们主要介绍一下soundjs和Web-Audio-API实现多音频播放的方法,文末还总结了两种实现方式的demo,大家可以依据实际项目情况添加接口。

 

| 导语 再回首-“凤凰传奇邀你唱H5”

 

前言

 

需求背景:用户选择玲花或曾毅,参与合唱,合唱成功后上传用户录音数据,封装待播放的音频信息(这些部分都是由林雨哥[lennylin]完成的),到落版页请求歌曲进行播放。其中ios微信侧录制音频会屏蔽掉媒体本身的声音,所以ios播放用户合唱歌曲需要同时播放用户录制音频和背景音乐。

  (凤凰传奇邀你唱H5)  

 

因为HTML5 Audio不支持同时播放多首歌,今天我们主要介绍一下soundjs和Web-Audio-API实现多音频播放的方法,文末还总结了两种实现方式的demo,大家可以依据实际项目情况添加接口。                                           

目录

 

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

UNSENT (未打开)

open()方法还未被调用.

1

OPENED  (未发送)

open()方法已被调用.

2

HEADERS_RECEIVED (已获取响应头)

send()方法已经被调用, 响应头和响应状态已经返回.

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多音频播放

 

仔细分析soundjs源码,你会发现它对音频的操作其实是基于强大的Web-Audio-API,而且大多数浏览器都支持Web-Audio-API,我们完全可以用它实现一个简单的音频播放器,不再依赖任何接口文件。
 
2.1 常用的接口:
 
AudioContext

 音频上下文

控制其包含节点的创建、处理和解码,使用其他接口之前需创建一个音频上下文

AudioNode  音频节点
AudioBuffer

音频数据对象

可以通过AudioContext.createBuffer 来创建或者

通过 AudioContext.decodeAudioData成功解码音轨后获取.

AudioBufferSourceNode

含有音频源的音频节点,它是用AudioBuffer对象来播放音频数据

可以通过createBufferSource() 方法创建

方法: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. 音频播放进度条实现

svg的stroke-dasharray属性:stroke-dasharray="0,10000",其中第一个属性是虚线宽度,第二个是虚线之间间隔,保证第二个属性大于圆形周长即可,然后动态改变第一个属性值,即可实现音乐播放进度条,其中第一个属性值是圆周长*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();
});

 

 

 

 


以上为“H5案例分享”团队原创文章,转载请注明出处!

标签

分享:
海报
案列链接
下载截图
收藏案例
已收藏
分享到微信朋友圈
打开微信,点击底部的“发现”,
使用“扫一扫”即可将网页分享至朋友圈。

相关推荐:

TGideas:微动效体验优化研究

TGideas:3D粒子效果在网页端实现分享

关注微信号:h5-share,获取更多创意H5案例分享!也可访问H5案例分享网站www.h5anli.com,搜索查阅~

阅读原文

微信扫一扫
关注该公众号

收藏

微信极速登录/注册

对汉语支持不错,别对小五说英文

微信公众号

提交案例

请pc端输入网址

"h5anli.com"

下载截图压缩包~

标签已提交成功

小编正在审核

请等待一下下啦

哎呀

标签已存在

换个新的呗~