Audio实战——击鼓并录制播放

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以此类推。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值