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编码解码

209 lines (183 loc) 7.53 kB
/* 录音 RecordApp: App Native支持文件,支持在浏览器环境中使用(Hybrid App)、各种适配后的js运行环境中使用(非浏览器环境) https://github.com/xiangyuecn/Recorder 特别注明:本文件涉及的功能需要iOS、Android等App端提供的原生支持,如果你不能修改App的源码,并且坚决要使用本文件,那将会很困难。 如果是在App内置的浏览器中进行录音(Hybrid App),应当首选使用Recorder H5进行录音;RecordApp+Native也可以在非浏览器环境中使用,比如:只有js运行时的app环境、nodejs环境。 录音功能由原生App(Native)代码实现,通过JsBridge和js进行交互。Native层需要提供:请求权限、开始录音、结束录音、定时回调PCM[Int16]片段 等功能和接口。因为js层已加载Recorder和相应的js编码引擎,所以,Native层无需进行编码,可大大简化App的逻辑。 录音必须是单声道的,因为这个库从头到尾就没有打算支持双声道。 JsBridge可以是自己实现的交互方式 或 别人提供的框架。因为不知道具体使用的桥接方式,对应的请求已抽象成了4个方法在Native.Config中,需自行实现。 注意:此文件并非拿来就能用的,需要改动【需实现】标注的地方;也可以不改动此文件,使用另外的初始化配置文件来进行配置,可参考app-support-sample目录内的配置文件,另外这个目录内还有Android和iOS的demo项目,copy源码改改就能用。 如果是App内置的浏览器中使用时(H5),支持在iframe中使用,但如果是跨域要特殊处理。 */ (function(factory){ var browser=typeof window=="object" && !!window.document; var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面 var rec=win.Recorder,ni=rec.i18n; factory(rec,ni,ni.$T,browser); }(function(Recorder,i18n,$T,isBrowser){ "use strict"; var App=Recorder.RecordApp; var CLog=App.CLog; var platform={ Support:function(call){ if(!App.AlwaysAppUseH5){ config.IsApp(call); return; }; //不支持app原生录音 call(false); } ,CanProcess:function(){ return true;//支持实时回调 } ,Config:{ IsApp:function(call){ //如需打开原生App支持,此方法【需实现】,此方法用来判断:1. 判断app是否是在环境中 2. app支持录音 NeedConfigMsg("IsApp"); call(false);//默认实现不支持app原生录音,支持就回调call(true) } ,JsBridgeRequestPermission:function(success,fail){ /*如需打开原生App支持,此方法【需实现】 success:fn() 有权限时回调 fail:fn(errMsg,isUserNotAllow) 出错回调 */ fail(NeedConfigMsg("JsBridgeRequestPermission")); } ,JsBridgeStart:function(set,success,fail){ /*如需打开原生App支持,此方法【需实现】,app打开录音后原生层定时返回PCM数据时JsBridge js层需要回调set.onProcess。建议JsBridge增加一个Alive接口,为录音时定时心跳请求,如果网页超过10秒未调用此接口,app原生层自动停止录音,防止stop不能调用导致的资源泄露。 set:RecordApp.Start的set参数 success:fn() 打开录音时回调 fail:fn(errMsg) 开启录音出错时回调 */ fail(NeedConfigMsg("JsBridgeStart")); } ,JsBridgeStop:function(success,fail){ /*如需打开原生App支持,此方法【需实现】 success:fn() 结束录音时回调 fail:fn(errMsg) 结束录音出错时回调 */ fail(NeedConfigMsg("JsBridgeStop")); } } }; App.RegisterPlatform("Native",platform); var config=platform.Config; var NeedConfigMsg=function(fn){ var msg=$T("WWoj::{1}中的{2}方法未实现,请在{3}文件中或配置文件中实现此方法",0,"RecordApp.Platforms.Native.Config",fn,"app-native-support.js"); CLog(msg,3); return msg; }; /*******App Native层在录音时定时回调本js方法*******/ /* pcmDataBase64: base64<Int16[]>字符串 当前单声道录音缓冲PCM片段,正常情况下为上次回调本接口开始到现在的录音数据,Int16[]二进制数组需要编码成base64字符串;或者直接传一个Int16Array对象 sampleRate:123456 录制音频实际的采样率 */ var onRecFn=function(pcmDataBase64,sampleRate){ var rec=onRecFn.rec; if(!rec){ CLog($T("rCAM::未开始录音,但收到Native PCM数据"),3); return; }; if(!rec._appStart){ rec.envStart({ envName:platform.Key,canProcess:platform.CanProcess() },sampleRate); }; rec._appStart=1; var sum=0; if(pcmDataBase64 instanceof Int16Array){ var pcm=new Int16Array(pcmDataBase64); for(var i=0;i<pcm.length;i++){ sum+=Math.abs(pcm[i]); } }else{ var bstr=atob(pcmDataBase64),n=bstr.length; var pcm=new Int16Array(n/2); for(var idx=0,s,i=0;i+2<=n;idx++,i+=2){ s=((bstr.charCodeAt(i)|(bstr.charCodeAt(i+1)<<8))<<16)>>16; pcm[idx]=s; sum+=Math.abs(s); }; } rec.envIn(pcm,sum); }; if(!isBrowser){ App.NativeRecordReceivePCM=onRecFn; }; //尝试注入顶层window,用于接收Native回调数据,此处特殊处理一下,省得跨域的iframe无权限 if(isBrowser){ window.NativeRecordReceivePCM=onRecFn; try{ window.top.NativeRecordReceivePCM=onRecFn; }catch(e){ var tipsFn=function(){ CLog($T("t2OF::检测到跨域iframe,NativeRecordReceivePCM无法注入到顶层,已监听postMessage转发兼容传输数据,请自行实现将top层接收到数据转发到本iframe(不限层),不然无法接收到录音数据"),3); }; setTimeout(tipsFn,8000); tipsFn(); addEventListener("message",function(e){//纯天然,无需考虑origin var data=e.data;//{type:"",data:{pcmDataBase64:"",sampleRate:16000}} if(data&&data.type=="NativeRecordReceivePCM"){ data=data.data; onRecFn(data.pcmDataBase64, data.sampleRate); }; }); }; }; /*******实现统一接口*******/ platform.RequestPermission=function(sid,success,fail){ config.JsBridgeRequestPermission(success,fail); }; platform.Start=function(sid,set,success,fail){ onRecFn.param=set; var rec=Recorder(set); rec.set.disableEnvInFix=true; //不要音频输入丢失补偿 rec.dataType="arraybuffer"; onRecFn.rec=rec;//等待第一个数据到来再调用rec.start App.__Rec=rec;//App需要暴露出使用到的rec实例 config.JsBridgeStart(set,success,fail); }; platform.Stop=function(sid,success,fail){ var failCall=function(msg){ if(App.__Sync(sid)){ onRecFn.rec=null; } fail(msg); }; config.JsBridgeStop(function(){ if(!App.__Sync(sid)){ failCall("Incorrect sync status"); return; }; var rec=onRecFn.rec; onRecFn.rec=null; var clearMsg=success?"":App.__StopOnlyClearMsg(); if(!rec){ failCall($T("Z2y2::未开始录音") +(clearMsg?" ("+clearMsg+")":"")); return; }; CLog("rec encode: pcm:"+rec.recSize+" srcSR:"+rec.srcSampleRate+" set:"+JSON.stringify(onRecFn.param)); var end=function(){ if(App.__Sync(sid)){ //把可能变更的配置写回去 for(var k in rec.set){ onRecFn.param[k]=rec.set[k]; }; }; }; if(!success){ end(); failCall(clearMsg); return; }; rec.stop(function(arrBuf,duration,mime){ end(); success(arrBuf,duration,mime); },function(msg){ end(); failCall(msg); }); },failCall); }; }));