https://xiangyuecn.gitee.io/recorder/

引入 recorder-core

npm install recorder-core -S

在渲染进程模块的main.js中加入recorder-core组件

//加载必须要的core,demo简化起见采用的直接加载类库,实际使用时应当采用异步按需加载
import Recorder from 'recorder-core'
//需要使用到的音频格式编码引擎的js文件统统加载进来,这些引擎文件会比较大
import 'recorder-core/src/engine/mp3'
import 'recorder-core/src/engine/mp3-engine'

module.exports = {
    data(){
        return {
            Rec:Recorder
            ,type:"mp3"
            ,bitRate:16
            ,sampleRate:16000
            ,rec:0
            ,duration:0
            ,powerLevel:0
            ,recOpenDialogShow:0
            ,logs:[]
        }
    }
    ,methods:{
        recOpen:function(){
            var This=this;
            var rec=this.rec=Recorder({
                type:This.type
                ,bitRate:This.bitRate
                ,sampleRate:This.sampleRate
                ,onProcess:function(buffers,powerLevel,duration,sampleRate){
                    This.duration=duration;
                    This.powerLevel=powerLevel;

                    This.wave.input(buffers[buffers.length-1],powerLevel,sampleRate);
                }
            });

            This.dialogInt=setTimeout(function(){//定时8秒后打开弹窗,用于监测浏览器没有发起权限请求的情况
                This.showDialog();
            },8000);

            rec.open(function(){
                This.dialogCancel();
                This.reclog("已打开:"+This.type+" "+This.sampleRate+"hz "+This.bitRate+"kbps",2);

                This.wave=Recorder.WaveView({elem:".ctrlProcessWave"});
            },function(msg,isUserNotAllow){
                This.dialogCancel();
                This.reclog((isUserNotAllow?"UserNotAllow,":"")+"打开失败:"+msg,1);
            });

            This.waitDialogClickFn=function(){
                This.dialogCancel();
                This.reclog("打开失败:权限请求被忽略,用户主动点击的弹窗",1);
            };
        }
        ,recClose:function(){
            var rec=this.rec;
            this.rec=null;
            if(rec){
                rec.close();
                this.reclog("已关闭");
            }else{
                this.reclog("未打开录音",1);
            };
        }
        ,recStart:function(){
            if(!this.rec||!Recorder.IsOpen()){
                this.reclog("未打开录音",1);
                return;
            }
            this.rec.start();

            var set=this.rec.set;
            this.reclog("录制中:"+set.type+" "+set.sampleRate+"hz "+set.bitRate+"kbps");
        }
        ,recPause:function(){
            if(this.rec&&Recorder.IsOpen()){
                this.rec.pause();
            }else{
                this.reclog("未打开录音",1);
            };
        }
        ,recResume:function(){
            if(this.rec&&Recorder.IsOpen()){
                this.rec.resume();
            }else{
                this.reclog("未打开录音",1);
            };
        }
        ,recStop:function(){
            if(!(this.rec&&Recorder.IsOpen())){
                This.reclog("未打开录音",1);
                return;
            }

            var This=this;
            var rec=This.rec;
            rec.stop(function(blob,duration){
                This.reclog("已录制:","",{
                    blob:blob
                    ,duration:duration
                    ,rec:rec
                });
            },function(s){
                This.reclog("录音失败:"+s,1);
            });
        }





        ,recPlayLast:function(){
            if(!this.recLogLast){
                this.reclog("请先录音,然后停止后再播放",1);
                return;
            };
            this.recplay(this.recLogLast.idx);
        }
        ,recUploadLast:function(){
            if(!this.recLogLast){
                this.reclog("请先录音,然后停止后再上传",1);
                return;
            };
            var This=this;
            var blob=this.recLogLast.res.blob;

            //本例子假设使用原始XMLHttpRequest请求方式,实际使用中自行调整为自己的请求方式
            //录音结束时拿到了blob文件对象,可以用FileReader读取出内容,或者用FormData上传
            var api="https://xx.xx/test_request";
            var onreadystatechange=function(title){
                return function(){
                    if(xhr.readyState==4){
                        if(xhr.status==200){
                            This.reclog(title+"上传成功",2);
                        }else{
                            This.reclog(title+"没有完成上传,演示上传地址无需关注上传结果,只要浏览器控制台内Network面板内看到的请求数据结构是预期的就ok了。", "#d8c1a0");

                            console.error(title+"上传失败",xhr.status,xhr.responseText);
                        };
                    };
                };
            };
            This.reclog("开始上传到"+api+",请求稍后...","#f60");

            /***方式一:将blob文件转成base64纯文本编码,使用普通application/x-www-form-urlencoded表单上传***/
            var reader=new FileReader();
            reader.onloadend=function(){
                var postData="";
                postData+="mime="+encodeURIComponent(blob.type);//告诉后端,这个录音是什么格式的,可能前后端都固定的mp3可以不用写
                postData+="&upfile_b64="+encodeURIComponent((/.+;\s*base64\s*,\s*(.+)$/i.exec(reader.result)||[])[1]) //录音文件内容,后端进行base64解码成二进制
                //...其他表单参数

                var xhr=new XMLHttpRequest();
                xhr.open("POST", api);
                xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
                xhr.onreadystatechange=onreadystatechange("上传方式一【Base64】");
                xhr.send(postData);
            };
            reader.readAsDataURL(blob);

            /***方式二:使用FormData用multipart/form-data表单上传文件***/
            var form=new FormData();
            form.append("upfile",blob,"recorder.mp3"); //和普通form表单并无二致,后端接收到upfile参数的文件,文件名为recorder.mp3
            //...其他表单参数

            var xhr=new XMLHttpRequest();
            xhr.open("POST", api);
            xhr.onreadystatechange=onreadystatechange("上传方式二【FormData】");
            xhr.send(form);
        }










        ,reclog:function(msg,color,res){
            var obj={
                idx:this.logs.length
                ,msg:msg
                ,color:color
                ,res:res

                ,playMsg:""
                ,down:0
                ,down64Val:""
            };
            if(res&&res.blob){
                this.recLogLast=obj;
            };
            this.logs.splice(0,0,obj);
        }
        ,recplay:function(idx){
            var This=this;
            var o=this.logs[this.logs.length-idx-1];
            o.play=(o.play||0)+1;
            var logmsg=function(msg){
                o.playMsg='<span style="color:green">'+o.play+'</span> '+This.getTime()+" "+msg;
            };
            logmsg("");

            var audio=this.$refs.LogAudioPlayer;
            audio.controls=true;
            if(!(audio.ended || audio.paused)){
                audio.pause();
            };
            audio.onerror=function(e){
                logmsg('<span style="color:red">播放失败['+audio.error.code+']'+audio.error.message+'</span>');
            };
            audio.src=(window.URL||webkitURL).createObjectURL(o.res.blob);
            audio.play();
        }
        ,recdown:function(idx){
            var This=this;
            var o=this.logs[this.logs.length-idx-1];
            o.down=(o.down||0)+1;

            o=o.res;
            var name="rec-"+o.duration+"ms-"+(o.rec.set.bitRate||"-")+"kbps-"+(o.rec.set.sampleRate||"-")+"hz."+(o.rec.set.type||(/\w+$/.exec(o.blob.type)||[])[0]||"unknown");
            var downA=document.createElement("A");
            downA.href=(window.URL||webkitURL).createObjectURL(o.blob);
            downA.download=name;
            downA.click();
        }
        ,recdown64:function(idx){
            var This=this;
            var o=this.logs[this.logs.length-idx-1];
            var reader = new FileReader();
            reader.onloadend = function() {
                o.down64Val=reader.result;
            };
            reader.readAsDataURL(o.res.blob);
        }
        ,getTime:function(){
            var now=new Date();
            var t=("0"+now.getHours()).substr(-2)
                +":"+("0"+now.getMinutes()).substr(-2)
                +":"+("0"+now.getSeconds()).substr(-2);
            return t;
        }
        ,intp:function(s,len){
            s=s==null?"-":s+"";
            if(s.length>=len)return s;
            return ("_______"+s).substr(-len);
        }


        ,showDialog:function(){
            //我们可以选择性的弹一个对话框:为了防止移动端浏览器存在第三种情况:用户忽略,并且(或者国产系统UC系)浏览器没有任何回调
            if(!/mobile/i.test(navigator.userAgent)){
                return;//只在移动端开启没有权限请求的检测
            };
            this.recOpenDialogShow=1;
        }
        ,dialogCancel:function(){
            clearTimeout(this.dialogInt);
            this.recOpenDialogShow=0;
        }
        ,waitDialogClick:function(){
            this.dialogCancel();
            this.waitDialogClickFn();
        }
    }
}
作者:Jeebiz  创建时间:2023-07-07 20:38
最后编辑:Jeebiz  更新时间:2024-10-29 20:36