1. 背景及预备知识
(1) 需求
用户自定义一段击鼓的声音,并录制下来。之后,可以将这段录制的声音播放出来。
- 敲击方式有三种:鼓中心、鼓边缘、鼓槌。
- 播放时,需要复现和之前用户操作敲击的声音和节奏一致。
(2) 了解audio用法
请参见之前完成的博文:
http://blog.csdn.net/joyce_lcy/article/details/79317397
2. 实战
(1) 实现思路
要录制声音,不可能真的将声音录下来成完整音频,而是记录用户的操作,敲了哪个位置,停顿了多长时间,然后又敲了哪个位置,从而得到一个序列。最终将这个序列复刻出来。
所以,我们得到的序列应该是这样的:[454, "edge", 466, "center", 493, "stick"]
(2) 实现过程
[1] 结构
搭建html如下图,实际项目中是不需要显示出audio的controls的。在这里,只是为了更直接得看到播放。
点击edge、center、stick分别播放对应位置的鼓声。点击record开始记录,用户在这其间可以任意敲击上述位置。点击play复刻用户敲打序列,点击show code在控制台中打印出生成的用户操作序列。
[2] 播放对应敲打声音
传入参数播放不同位置的音频。
function playMusic(key){
switch(key){
case 'edge':
$edge.play();
break;
case 'center':
$center.play();
break;
case 'stick':
$stick.play();
break;
}
}
[3] 记录序列
getTime函数返回的是这样的值:Mon Feb 26 2018 15:16:27 GMT+0800 (CST)
time是个全局变量,在点击record时会调用reset函数。time第一次被赋值,记录了点击时的时间。每次点击,time都会被赋值为这次点击的时间。
writeRecord函数将每次的时间差及敲打的位置写入record这个数组中。
var record=[];
var time;
function reset(){
record=[];
time=getTime();
}
function getTime(){
var _time=new Date();
return _time;
}
function writeRecord(name){
var timeNow;
timeNow=getTime();
record.push(timeNow - time);
record.push(name);
time=timeNow;
}
[4] 播放序列
playtime是个全局变量的开关,作用是在序列播放完毕前关闭播放功能。等到序列全部播放完成,开关才会重新打开。
遍历record数组,根据上述内容,第一个值是间隔时间,第二个是敲打的位置,以此类推。所以,record[i]
是时间间隔,record[i+1]
是敲打的位置。
循环是不会执行setTimeout而停在那,等到完成后再进行下个循环的。而是for循环跑完,setTimeout全部注册在那。所以,setTimeout等待的时间是前面所有间隔的时间之和。
var playtime=false;
function playRecord(){
var timeout=0;
if(!playtime){
playtime=true;
$btn_play.style.color='#aaa';
$btn_play.style.cursor='not-allowed';
for(var i=0;i<record.length;i++){
var _sound=record[i+1];
timeout+=record[i];
setTimeout("playMusic('"+_sound+"')",timeout);
i++;
}
setTimeout(function(){
playtime=false;
$btn_play.style.color='#363636';
$btn_play.style.cursor='pointer';
},timeout);
}
}
[5] 事件注册
由于手机端对于click事件会有300ms的延迟,从而影响用户的操作体验,所以改用touchstart事件。又因这个事件在desktop端无效,所以需要使用isMobile函数进行判断,分别挂载事件。
function isMobile(){
return /Mobile/i.test(navigator.userAgent);
}
// 注册事件,desktop是click,而mobile是touchstart
var eventName=isMobile()?'touchstart':'click',
$edge=document.getElementById("edge"),
$center=document.getElementById("center"),
$stick=document.getElementById("stick"),
$btn_record=document.getElementById("btn-record"),
$btn_show=document.getElementById("btn-show"),
$btn_edge=document.getElementById("btn-edge"),
$btn_center=document.getElementById("btn-center"),
$btn_stick=document.getElementById("btn-stick"),
$btn_play=document.getElementById("btn-play");
$btn_edge.addEventListener(eventName, function(){
playMusic('edge');
writeRecord('edge');
}, false);
$btn_center.addEventListener(eventName, function(){
playMusic('center');
writeRecord('center');
}, false);
$btn_stick.addEventListener(eventName, function(){
playMusic('stick');
writeRecord('stick');
}, false);
$btn_record.addEventListener(eventName, reset, false);
$btn_show.addEventListener(eventName, function(){
console.log(record);
}, false);
$btn_play.addEventListener(eventName, playRecord,false);
(3) 完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<title>audio demo</title>
<style>
button{display: inline-block;border: solid 1px #363636; color: #363636; margin: 5px; cursor: pointer;outline:none;}
button:hover{ background-color: #eeeeee;}
</style>
</head>
<body>
<audio id="edge" src="http://dx.sc.chinaz.com/Files/DownLoad/sound/huang/cd3/mp3/b8/03.MP3" controls >
您的浏览器不支持 audio 元素。
</audio>
<audio id="center" src="http://fjdx.sc.chinaz.com/files/download/sound1/201312/3845.mp3" controls>
您的浏览器不支持 audio 元素。
</audio>
<audio id="stick" src="http://fjdx.sc.chinaz.com/files/download/sound1/201310/3692.mp3" controls>
您的浏览器不支持 audio 元素。
</audio>
<div>
<button id="btn-record">record</button>
<button id="btn-show">show code</button>
<br>
<button id="btn-edge">edge</button>
<button id="btn-center">center</button>
<button id="btn-stick">stick</button>
<br>
<button id="btn-play">play</button>
</div>
<script>
var record=[],
time,
playtime=false,
// 注册事件,desktop是click,而mobile是touchstart
eventName=isMobile()?'touchstart':'click',
$edge=document.getElementById("edge"),
$center=document.getElementById("center"),
$stick=document.getElementById("stick"),
$btn_record=document.getElementById("btn-record"),
$btn_show=document.getElementById("btn-show"),
$btn_edge=document.getElementById("btn-edge"),
$btn_center=document.getElementById("btn-center"),
$btn_stick=document.getElementById("btn-stick"),
$btn_play=document.getElementById("btn-play");
$btn_edge.addEventListener(eventName, function(){
playMusic('edge');
writeRecord('edge');
}, false);
$btn_center.addEventListener(eventName, function(){
playMusic('center');
writeRecord('center');
}, false);
$btn_stick.addEventListener(eventName, function(){
playMusic('stick');
writeRecord('stick');
}, false);
$btn_record.addEventListener(eventName, reset, false);
$btn_show.addEventListener(eventName, function(){
console.log(record);
}, false);
$btn_play.addEventListener(eventName, playRecord,false);
//-functions-
function isMobile(){
return /Mobile/i.test(navigator.userAgent);
}
function playMusic(key){
switch(key){
case 'edge':
$edge.play();
break;
case 'center':
$center.play();
break;
case 'stick':
$stick.play();
break;
}
}
function getTime(){
var _time=new Date();
return _time;
}
function writeRecord(name){
var timeNow;
timeNow=getTime();
record.push(timeNow - time);
record.push(name);
time=timeNow;
}
function reset(){
record=[];
time=getTime();
}
function playRecord(){
var timeout=0;
if(!playtime){
playtime=true;
$btn_play.style.color='#aaa';
$btn_play.style.cursor='not-allowed';
for(var i=0;i<record.length;i++){
var _sound=record[i+1];
timeout+=record[i];
setTimeout("playMusic('"+_sound+"')",timeout);
i++;
}
setTimeout(function(){
playtime=false;
$btn_play.style.color='#363636';
$btn_play.style.cursor='pointer';
},timeout);
}
}
</script>
</body>
</html>
3. 混响
上例中,我们实现的是单音轨播放,也就是说不会有两个声音同时播放。如果要做混响可以实现么?
注:以下例子给的ES6的写法。
(1) 通过js创建Audio
由于每次都创建的Audio的实例,所以播放时不会相互干扰,可以多音轨播放。
let audio = new Audio();
audio.src = '../../../static/audio/'+key+'.ogg';
if(audio.canPlayType('audio/ogg')==''){
audio.src = '../../../static/audio/'+key+'.mp3';
}
audio.play();
audio.onended=()=>{
audio.src=null;
}
这样的方式在本例中是不会有问题的。但是往往项目需要加载时就播放之前某个用户录好的某个序列。在手机端,因为safari等不允许自动播放音频和视频必须要用户交互时才能播放。
(2) html创建audio滚动播放
如果存在上述情况,可以创建多个audio的DOM节点,加载相同的音频文件。根据项目的实际情况,确定audio的个数。
因为audio并非js创建,是有限的。所以,在页面加载的时候就通过背景音乐自动播放的原理,让需要播放的音频播放再暂停。就可以解决不能自动播放序列的问题。
那如何实现多音轨呢?
例如创建了5个相同audio节点,第一次播放的时候播放第一个audio,第二次播放第二个audio以此类推。