UNPKG

recorder-core

Version:

Recorder库: html5 js 录音 mp3 wav ogg webm amr g711a g711u 格式,支持pc和Android、iOS部分浏览器、Hybrid App(提供Android iOS App源码)、微信,提供ASR语音识别转文字 H5版语音通话聊天示例 DTMF编码解码

450 lines (397 loc) 14 kB
/* RecordApp:基于Recorder的跨平台录音,支持在浏览器环境中使用(H5)、各种使用js来构建的程序中使用(App、小程序、UniApp、Electron、NodeJs) https://github.com/xiangyuecn/Recorder 示例demo请参考根目录内的app-support-sample目录 使用时需先引入recorder-core和需要的编码器,再引入本js,再根据不同平台引入相应的app-xxx-support.js支持文件;如果引入的支持文件需要进行额外的配置,可参考app-support-sample目录内对应的配置文件。 可以仅使用RecordApp作为入口,可以不关心recorder-core中的方法,因为RecordApp已经对他进行了一次封装,并且屏蔽了非通用的功能。 */ (function(factory){ var browser=typeof window=="object" && !!window.document; var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面 var rec=win.Recorder,ni=rec.i18n; factory(win,rec,ni,ni.$T,browser); //umd returnExports.js if(typeof(define)=='function' && define.amd){ define(function(){ return win.RecordApp; }); }; if(typeof(module)=='object' && module.exports){ module.exports=win.RecordApp; }; }(function(Export,Recorder,i18n,$T,isBrowser){ "use strict"; var App={ LM:"2024-04-09 19:22" //当前使用的平台实现 ,Current:0 //已注册的平台实现 ,Platforms:{} }; var Platforms=App.Platforms; var AppTxt="RecordApp"; var ReqTxt="RequestPermission"; var RegTxt="RegisterPlatform"; var WApp2=Export[AppTxt];//重复加载js if(WApp2&&WApp2.LM==App.LM){ WApp2.CLog($T("uXtA::重复导入{1}",0,AppTxt),3); return; }; Export[AppTxt]=App; Recorder[AppTxt]=App; App.__SID_=0;//同步操作,防止同时多次调用 var SID=App.__SID=function(){ return ++App.__SID_; }; var Sync=App.__Sync=function(sid,tag,err){ if(App.__SID_!=sid){ if(tag){ CLog($T("kIBu::注意:因为并发调用了其他录音相关方法,当前 {1} 的调用结果已被丢弃且不会有回调",0,tag)+(err?", error: "+err:""),3); } return false; } return true; }; var CLog=function(){ var v=arguments; v[0]="["+(CLog.Tag||AppTxt)+"]["+(App.Current&&App.Current.Key||"?")+"]"+v[0]; Recorder.CLog.apply(null,v); }; App.CLog=CLog; /** 注册一个平台的实现,对应的都会有一个app-xxx-support.js支持文件(Default-H5除外),config中提供统一的实现接口: { Support: fn( call(canUse) ) 判断此平台是否支持或开启,如果平台可用需回调call(true)选择使用这个平台,并忽略其他平台 CanProcess: fn() 此平台是否支持实时回调,返回true代表支持 Install: fn( success(),fail(errMsg) ) 可选,平台初始化安装,当使用此平台时会执行一次本方法(同一时间只会有一次调用,没有并发调用问题) Pause: fn() 可选,暂停录音实现,如果返回false将执行默认暂停操作 Resume: fn() 可选,继续录音实现,如果返回false将执行默认继续操作 下面的方法中sid用于同步操作,在异步回调中用App.__Sync判断此sid是否处于同步状态 实现中使用到的Recorder实例需赋值给App.__Rec(Stop结束后会自动清理并赋值为null) RequestPermission:fn(sid,success,fail) 实现录音权限请求,通过回调函数返回结果 success:fn() 有权限时回调 fail:fn(errMsg,isUserNotAllow) 没有权限或者不能录音时回调,如果是用户主动拒绝的录音权限,除了有错误消息外,isUserNotAllow=true,方便程序中做不同的提示,提升用户主动授权概率 Start:fn(sid,set,success,fail) 实现开始录音 set:{} 和Recorder的set大部分参数相同 success:fn() 打开录音时回调 fail:fn(errMsg) 开启录音出错时回调 Start_Check:fn(set) 可选,调用本实现的Start前执行环境检查,返回检查错误文本,如果返回false将执行默认检查操作 AllStart_Clean:fn(set) 可选,任何实现的Start前执行本配置清理,set里面可能为了兼容不同平台环境会传入额外的参数,可以进行清理,无返回值 Stop:fn(sid,success,fail) 实现结束录音,返回结果,success参数=null时仅清理资源 success:fn(arrayBuffer,duration,mime) 成功完成录音回调,参数可能为null arrayBuffer:ArrayBuffer 录音数据 duration:123 //录音数据持续时间 mime:"audio/mp3" 录音数据格式 fail:fn(errMsg) 录音出错时回调 } **/ App[RegTxt]=function(key,config){ //App.RegisterPlatform=function() config.Key=key; if(Platforms[key]){ CLog($T("ha2K::重复注册{1}",0,key),3); } Platforms[key]=config; }; App.__StopOnlyClearMsg=function(){ return $T("wpTL::仅清理资源"); }; /****实现默认的H5统一接口*****/ var KeyH5="Default-H5",H5OpenSet=ReqTxt+"_H5OpenSet"; (function(){ var impl={ Support:function(call){ //默认的始终要支持 call(true); } ,CanProcess:function(){ return true;//支持实时回调 } }; App[RegTxt](KeyH5,impl); impl[ReqTxt]=function(sid,success,fail){ //impl.RequestPermission=function() var old=App.__Rec; if(old){ old.close(); App.__Rec=null; }; h5Kill(); //h5会提前打开录音,open时需要的配置只能单独配置 var h5Set=App[H5OpenSet]; App[H5OpenSet]=null; var rec=Recorder(h5Set||{}); rec.open(function(){ //rec.close(); keep stream Stop时再关,免得Start再次请求权限 success(); },fail); }; var h5Kill=function(){ //释放检测权限时已打开的录音 if(Recorder.IsOpen()){ CLog("kill open..."); var rec=Recorder(); rec.open(); rec.close(); }; }; impl.Start=function(sid,set,success,fail){ var appRec=App.__Rec; if(appRec!=null){ appRec.stop();//未stop的stop掉 }; App.__Rec=appRec=Recorder(set); appRec.appSet=set; appRec.dataType="arraybuffer"; appRec.open(function(){ if(Sync(sid)){ appRec.start(); }; success(); },fail); }; impl.Stop=function(sid,success,fail){ var appRec=App.__Rec; var clearMsg=success?"":App.__StopOnlyClearMsg(); if(!appRec){ h5Kill(); //释放检测权限时已打开的录音 fail($T("bpvP::未开始录音") +(clearMsg?" ("+clearMsg+")":"")); return; }; var end=function(){ if(Sync(sid)){ appRec.close(); //把可能变更的配置写回去 for(var k in appRec.set){ appRec.appSet[k]=appRec.set[k]; }; }; }; var stopFail=function(msg){ end(); fail(msg); }; if(!success){ stopFail(clearMsg); return; }; appRec.stop(function(arrBuf,duration,mime){ end(); success(arrBuf,duration,mime); },stopFail); }; })(); /*** 获取底层平台录音过程中会使用用来处理实时数据的Recorder对象实例rec,如果底层录音过程中不使用Recorder进行数据的实时处理(目前没有),将返回null。Start调用前和Stop调用后均会返回null。 rec中的方法不一定都能使用,主要用来获取内部缓冲用的,比如实时清理缓冲。 ***/ App.GetCurrentRecOrNull=function(){ return App.__Rec||null; }; /**暂停录音**/ App.Pause=function(){ var cur=App.Current,key="Pause"; if(cur&&cur[key]){ if(cur[key]()!==false)return; } var rec=App.__Rec; if(rec && canProc(key)){ rec.pause(); } }; /**恢复录音**/ App.Resume=function(){ var cur=App.Current,key="Resume"; if(cur&&cur[key]){ if(cur[key]()!==false)return; } var rec=App.__Rec; if(rec && canProc(key)){ rec.resume(); } }; var canProc=function(tag){ var cur=App.Current; if(cur&&cur.CanProcess()) return 1; CLog($T("fLJD::当前环境不支持实时回调,无法进行{1}",0,tag),3); }; /*** 初始化安装,可反复调用 success: fn() 初始化成功 fail: fn(msg) 初始化失败 ***/ App.Install=function(success,fail){ var cur=App.Current; if(cur){ success&&success(); return; } //因为此操作是异步的,为了避免竞争Current资源,此代码保证得到结果前只会发起一次调用 var reqs=App.__reqs||(App.__reqs=[]); reqs.push({s:success,f:fail}); success=function(){ call("s",arguments) }; fail=function(){ call("f",arguments) }; var call=function(fn,args){ var arr=[].concat(reqs); reqs.length=0; for(var i=0;i<arr.length;i++){ var f=arr[i][fn]; f&&f.apply(null,args); }; }; if(reqs.length>1) return; var keys=[KeyH5],key; for(var k in Platforms){ if(k!=KeyH5)keys.push(k); } keys.reverse(); var initCur=function(idx){ key=keys[idx]; cur=Platforms[key]; cur.Support(function(canUse){ if(!canUse){ return initCur(idx+1); }; if(cur.Install){ cur.Install(initOk,fail); }else{ initOk(); }; }); }; var initOk=function(){ App.Current=cur; CLog("Install platform: "+key); success(); }; initCur(0); }; /*** 请求录音权限,如果当前环境不支持录音或用户拒绝将调用错误回调,调用start前需先至少调用一次此方法;请求权限后如果不使用了,不管有没有调用Start,至少要调用一次Stop来清理可能持有的资源。 success:fn() 有权限时回调 fail:fn(errMsg,isUserNotAllow) 没有权限或者不能录音时回调,如果是用户主动拒绝的录音权限,除了有错误消息外,isUserNotAllow=true,方便程序中做不同的提示,提升用户主动授权概率 ***/ App[ReqTxt]=function(success,fail){ //App.RequestPermission=function(success,fail){ var sid=SID(),tag=AppTxt+"."+ReqTxt; var failCall=function(errMsg,isUserNotAllow){ isUserNotAllow=!!isUserNotAllow; var msg=errMsg+", isUserNotAllow:"+isUserNotAllow; if(!Sync(sid,tag,msg))return; CLog($T("YnzX::录音权限请求失败:")+msg,1); fail&&fail(errMsg,isUserNotAllow); }; CLog(ReqTxt+"..."); App.Install(function(){ if(!Sync(sid,tag))return; var checkMsg=CheckH5(); if(checkMsg){ failCall(checkMsg); return; }; App.Current[ReqTxt](sid,function(){ //App.Current.RequestPermission() if(!Sync(sid,tag))return; CLog(ReqTxt+" Success"); success&&success(); },failCall); },failCall); }; var NeedReqMsg=function(){ return $T("nwKR::需先调用{1}",0,ReqTxt); }; var CheckH5=function(){ var msg=""; if(App.Current.Key==KeyH5 && !isBrowser){ msg=$T("citA::当前不是浏览器环境,需引入针对此平台的支持文件({1}),或调用{2}自行实现接入",0,"src/app-support/app-xxx-support.js",AppTxt+"."+RegTxt); }; return msg; }; /*** 开始录音,需先调用RequestPermission获得录音权限 set:设置默认值:{ type:"mp3"//最佳输出格式,如果底层实现能够支持就应当优先返回此格式 sampleRate:16000//最佳采样率hz bitRate:16//最佳比特率kbps onProcess:NOOP //如果当前环境支持实时回调(RecordApp.Current.CanProcess()),接收到录音数据时的回调函数:fn(buffers,powerLevel,bufferDuration,bufferSampleRate) takeoffEncodeChunk:NOOP //fn(chunkBytes) } 注意:此对象会被修改,因为平台实现时需要把实际使用的值存入此对象 success:fn() 打开录音时回调 fail:fn(errMsg) 开启录音出错时回调 ***/ App.Start=function(set,success,fail){ var sid=SID(),tag=AppTxt+".Start"; var failCall=function(msg){ if(!Sync(sid,tag,msg))return; CLog($T("ecp9::开始录音失败:")+msg,1); fail&&fail(msg); }; CLog("Start..."); var cur=App.Current; if(!cur){ failCall(NeedReqMsg()); return; }; set||(set={}); var obj={ type:"mp3" ,sampleRate:16000 ,bitRate:16 ,onProcess:function(){} }; for(var k in obj){ set[k]||(set[k]=obj[k]); }; //对配置项进行清理,set里面可能为了兼容不同平台环境会传入额外的参数,可以进行清理 for(var k in Platforms){ var pf=Platforms[k]; if(pf.AllStart_Clean){ pf.AllStart_Clean(set); } }; //先执行一下环境配置检查 var checkMsg=false; if(cur.Start_Check){ checkMsg=cur.Start_Check(set); }; if(checkMsg===false){ var checkRec=Recorder(set); checkMsg=checkRec.envCheck({envName:cur.Key,canProcess:cur.CanProcess()}); if(!checkMsg) checkMsg=CheckH5(); } if(checkMsg){ failCall($T("EKmS::不能录音:")+checkMsg); return; }; //重置Stop时的rec App._SRec=0; cur.Start(sid,set,function(){ if(!Sync(sid,tag))return; CLog($T("k7Qo::已开始录音"),set); App._STime=Date.now(); success&&success(); },failCall); }; /*** 结束录音 success:fn(arrayBuffer,duration,mime) 结束录音时回调 arrayBuffer:ArrayBuffer 录音二进制数据 duration:123 录音时长,单位毫秒 mime:"auido/mp3" 录音格式类型 fail:fn(errMsg) 录音出错时回调 本方法可以用来清理持有的资源,如果不提供success参数=null时,将不会进行音频编码操作,只进行清理完可能持有的资源后走fail回调 ***/ App.Stop=function(success,fail){ var sid=SID(),tag=AppTxt+".Stop"; var failCall=function(msg){ if(!Sync(sid,tag,msg))return; CLog($T("Douz::结束录音失败:")+msg,success?1:0); try{ fail&&fail(msg); }finally{ clear() } }; var clear=function(){ App._SRec=App.__Rec; App.__Rec=null; }; CLog("Stop... "+$T("wqSH::和Start时差:{1}ms",0,App._STime?Date.now()-App._STime:-1)+" Recorder.LM:"+Recorder.LM+" "+AppTxt+".LM:"+App.LM); var t1=Date.now(); var cur=App.Current; if(!cur){ failCall(NeedReqMsg()); return; }; cur.Stop(sid, !success?null:function(arrayBuffer,duration,mime){ if(!Sync(sid,tag))return; CLog($T("g3VX::结束录音 耗时{1}ms 音频时长{2}ms 文件大小{3}b {4}",0,Date.now()-t1,duration,arrayBuffer.byteLength,mime)); try{ success(arrayBuffer,duration,mime); }finally{ clear() } },failCall); }; }));