recorder-core-rlian
Version:
Recorder库:html5 js 录音 mp3 wav ogg webm amr 格式,支持pc和Android、ios部分浏览器、和Hybrid App(提供Android IOS App源码),微信也是支持的,提供H5版语音通话聊天示例 和DTMF编解码
134 lines (112 loc) • 5.1 kB
JavaScript
/*
录音 RecordApp: Hybrid App Native支持文件。
特别注明:本文件涉及的功能需要IOS、Android App端的支持,如果你不能修改App的源码,并且坚决要使用本文件,那将会很困难。
本文件诞生的原因是因为IOS端WKWebView(UIWebView)中未开放getUserMedia功能来录音,只能寻求其他解决方案。Android端没有此问题,但Android里面使用网页的录音权限问题可能比原生的权限机制要复杂,为了简化js端的复杂性(出问题了好甩锅),不管是Android还是IOS都实现一下可能会简单很多。当以后IOS任何地方的网页都能录音时,本文件就可以删除了。
录音功能由原生App(Native)代码实现,通过JsBridge和h5进行交互。Native层需要提供:请求权限、开始录音、结束录音、定时回调PCM[Int16]片段 这4个功能、接口。因为js层已自动加载Recorder和相应的js编码引擎,所以,Native层无需进行编码(Android和IOS的音频编码并非易事,且不易更新),大大简化App的逻辑和弹性。
录音必须是单声道的,因为这个库从头到尾就没有打算支持双声道。
JsBridge可以是自己实现的交互方式 或 别人提供的框架。因为不知道具体使用的桥接方式,对应的请求已抽象成了3个方法在Native.Config中,需自行实现。
本文件源码可以不用改动,因为需要改动的地方已放到了app.js的Native.Config中了;最终实际实现可参考app-support-sample目录内的配置文件,另外此目录内还有Android和IOS的demo项目,copy源码改改就能用。
支持在iframe中使用,但如果是跨域要特殊处理。
https://github.com/xiangyuecn/Recorder
*/
(function(){
;
var App=RecordApp;
var CLog=App.CLog;
var platform=App.Platforms.Native;
var config=platform.Config;
platform.IsInit=true;
/*******Hybrid App Native层在录音时定时回调本js方法*******/
/*
pcmDataBase64: base64<Int16[]>字符串 当前单声道录音缓冲PCM片段,正常情况下为上次回调本接口开始到现在的录音数据,Int16[]二进制数组需要编码成base64字符串
sampleRate:123456 录制音频实际的采样率
*/
var onRecFn=window.NativeRecordReceivePCM=function(pcmDataBase64,sampleRate){//无视iframe
var rec=onRecFn.rec;
if(!rec){
CLog("未开始录音,但收到Native PCM数据",3);
return;
};
if(!rec._appStart){
rec.envStart({
envName:platform.Key,canProcess:platform.CanProcess()
},sampleRate);
};
rec._appStart=1;
var bstr=atob(pcmDataBase64),n=bstr.length;
var pcm=new Int16Array(n/2);
var sum=0;
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);
};
//尝试注入顶层window,用于接收Native回调数据,此处特殊处理一下,省得跨域的iframe无权限
try{
window.top.NativeRecordReceivePCM=onRecFn;
}catch(e){
var tipsFn=function(){
CLog("检测到跨域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);
};
});
};
/*******实现统一接口,以下方法参数请参考app.js RecordApp中的同名方法*******/
platform.RequestPermission=function(success,fail){
config.JsBridgeRequestPermission(success,fail);
};
platform.Start=function(set,success,fail){
onRecFn.param=set;
var rec=Recorder(set);
rec.set.disableEnvInFix=true; //不要音频输入丢失补偿
onRecFn.rec=rec;//等待第一个数据到来再调用rec.start
App.__Rec=rec;//rec在stop时需要即时清理,因此暴露的内部变量需另外赋值
config.JsBridgeStart(set,success,fail);
};
platform.Stop=function(success,fail){
var failCall=function(msg){
fail("录音失败[Native]:"+msg);
onRecFn.rec=null;
App.__Rec=null;
};
config.JsBridgeStop(function(){
var rec=onRecFn.rec;
onRecFn.rec=null;
if(!rec){
failCall("未开始录音");
return;
};
CLog("rec encode start: pcm:"+rec.recSize+" src:"+rec.srcSampleRate+" set:"+JSON.stringify(onRecFn.param));
var end=function(){
//把可能变更的配置写回去
for(var k in rec.set){
onRecFn.param[k]=rec.set[k];
};
};
if(!success){
end();
failCall("仅清理资源");
return;
};
rec.stop(function(blob,duration){
CLog("rec encode end")
end();
App._SRec=rec;
success(blob,duration);
App.__Rec=null;
},function(msg){
end();
failCall(msg);
});
},failCall);
};
})();