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
JavaScript
/*
录音 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){
;
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);
};
}));