/** 实时语音通话聊天对讲,websocket实时传输数据 **/ (function(){ "use strict"; //这些代码大部分复制自 /app-support-sample/demo_UniApp/pages/recTest/test_realtime_voice.vue var PageObject=function(){ var data={ wsApi:"", wsID1:"", wsID2:"" ,ws_audioFrameCount:0, ws_audioFrameSize:0, ws_audioFrameDur:0, ws_audioFrameDurTxt:"" ,ws_voiceSendUserCount:0,ws_voiceSendOKCount:0,ws_voiceSendErrCount:0,ws_voiceSendSize:0,ws_voiceSendDur:0,ws_voiceSendDurTxt:"" ,ws_voiceReceiveCount:0,ws_voiceReceiveSize:0,ws_voiceReceiveDur:0,ws_voiceReceiveDurTxt:"" }; for(var k in data) this[k]=data[k]; }; PageObject.prototype={ setData:function(data){ for(var k in data) this[k]=data[k]; $(".rtVoice_audioFrameMsg").html(this.ws_audioFrameDurTxt?( this.ws_audioFrameDurTxt+','+this.ws_audioFrameCount+'帧,'+this.ws_audioFrameSize+'字节' ):""); var rs=this.ws_voiceReceiveDurTxt||this.ws_voiceSendDurTxt; $(".rtVoice_voiceReceiveMsg").html(rs?( '接收:'+this.ws_voiceReceiveDurTxt+','+this.ws_voiceReceiveCount+'帧,'+this.ws_voiceReceiveSize+'字节' ):""); $(".rtVoice_voiceSendMsg").html(rs?( '发送:'+this.ws_voiceSendDurTxt+','+this.ws_voiceSendUserCount+'人接收,OK '+this.ws_voiceSendOKCount+'帧,Err '+this.ws_voiceSendErrCount+'帧,'+this.ws_voiceSendSize+'字节' ):""); } ,log:function(){ reclog.apply(null,arguments); } ,formatTime:function(ms,showSS){ var ss=ms%1000;ms=(ms-ss)/1000; var s=ms%60;ms=(ms-s)/60; var m=ms%60;ms=(ms-m)/60; var h=ms, v=""; if(h>0) v+=(h<10?"0":"")+h+":"; v+=(m<10?"0":"")+m+":"; v+=(s<10?"0":"")+s; if(showSS)v+="″"+("00"+ss).substr(-3);; return v; } ,checkSet:function(api){ if(api==2){ this.wsApi=$(".rtVoice_input_ws").val(); if(!/^wss?:\/\/.+/i.test(this.wsApi) || /局域网/.test(this.wsApi)){ this.log("请配置ws地址,比如填写:ws://127.0.0.1:9529/",1); return false; } localStorage["Rec_RtVoice_wsApi"]=this.wsApi; //测试用的存起来 this.wsID1=$(".rtVoice_input_wsID1").val(); if(!this.wsID1){ this.log("请填写我的标识"); return false; } localStorage["Rec_RtVoice_wsID1"]=this.wsID1; //测试用的存起来 } return true; } //连接websocket ,wsConnClick:function(){ if(!this.checkSet(2))return; this.log("正在连接"+this.wsApi+"..."); if(this.socket) this.socket.close(); var sid=this.SID=(this.SID||0)+1; //同步操作 this.socketIsOpen=false; this.socket=new WebSocket(this.wsApi); this.socket.binaryType="arraybuffer"; this.socket.onclose=(function(){ if(sid!=this.SID) return; this.socketIsOpen=false; this.destroyStreamPlay(); this.log("ws已断开"); }).bind(this); this.socket.onerror=(function(e){ if(sid!=this.SID) return; this.socketIsOpen=false; this.destroyStreamPlay(); this.log("ws因为错误已断开:"+e.message,1); }).bind(this); this.socket.onopen=(function(){ if(sid!=this.SID) return; this.socketIsOpen=true; this.socket.number=1; this.socket.sendCalls={}; this.log("ws已连接",2); this.ws__sendMessage("setMeta",{uid:this.wsID1},null,null,(function(){ this.log("ws已绑定标识:"+this.wsID1,2); }).bind(this),(function(err){ this.log("ws绑定标识出错:"+err,1); }).bind(this)); this.ws_voiceReceiveCount=0; this.ws_voiceReceiveSize=0; this.ws_voiceReceiveDur=0; this.ws_voiceReceiveDurTxt="00:00"; this.resetWsSendVoice(); this.initStreamPlay(); //先初始化播放器 }).bind(this); this.socket.onmessage=(function(e){ if(sid!=this.SID) return; this.ws__receiveMessage(e.data); }).bind(this); } ,wsDisconnClick:function(){ if(this.socketIsOpen)this.log("ws正在断开..."); else this.log("ws未连接"); if(this.socket) this.socket.close(); } //解析处理服务器消息 ,ws__receiveMessage:function(rawMsg){ var binary=new Uint8Array(0),rawTxt=rawMsg;//纯文本消息 if (rawMsg instanceof ArrayBuffer) {//二进制内容,提取第一行文本 var bytes=new Uint8Array(rawMsg); var str="",bIdx=bytes.length; for(var i=0;inewBufferIdx){ chunk=null; //重新录音了,重置环境 } lastIdx=newBufferIdx; //借用SampleData函数进行数据的连续处理,采样率转换是顺带的,得到新的pcm数据 chunk=Recorder.SampleData(buffers,sampleRate,16000,chunk); var pcm=chunk.data; //二进制pcm var bytes=new Uint8Array(pcm.buffer); //发送pcm出去 this.ws__sendMessage("sendTo",{ toMetaKey:"uid",toMetaValue:this.wsID2 //接收方信息,可以是群组(群组的需控制同时只能一人发,否则多人得服务器端混流) ,sendType:"custom_voiceFrame",sendData:{ fromMetaKey:"uid",fromMetaValue:this.wsID1//告诉对方是我发的信息 ,sampleRate:16000 //采样率 } },bytes,null,(function(data){ this.ws_voiceSendUserCount=data.count; if(data.count){ this.ws_voiceSendOKCount++; }else{ //没有接收方 this.ws_voiceSendErrCount++; } this.ws_voiceSendSize+=pcm.byteLength; this.ws_voiceSendDur=Math.round(this.ws_voiceSendSize/2/16000*1000); this.ws_voiceSendDurTxt=this.formatTime(this.ws_voiceSendDur); this.setData(); }).bind(this)); }).bind(this); this.resetWsSendVoice(); } ,resetWsSendVoice:function(){ this.ws_voiceSendUserCount=0; this.ws_voiceSendOKCount=0; this.ws_voiceSendErrCount=0; this.ws_voiceSendSize=0; this.ws_voiceSendDur=0; this.ws_voiceSendDurTxt="00:00"; this.setData(); } //结束语音通话对讲 ,wsCloseVoiceClick:function(){ window.RtVoiceProcess=null; this.log("我方已结束语音发送"); } //收到对方发来的自定义类型的语音数据 ,onMsg__custom_voiceFrame:function(data,binary,msgObj){ var pcm=new Int16Array(binary.buffer); this.ws_voiceReceiveCount++; this.ws_voiceReceiveSize+=binary.length; this.ws_voiceReceiveDur=Math.round(this.ws_voiceReceiveSize/2/data.sampleRate*1000); this.ws_voiceReceiveDurTxt=this.formatTime(this.ws_voiceReceiveDur); this.setData(); this.streamPlay(pcm,data.sampleRate); } //播放实时的语音流 ,streamPlay:function(pcm,sampleRate){ this.initStreamPlay(); if(sampleRate!=16000){ console.warn("未适配非16000采样率的pcm播放:initStreamPlay中手写的16000采样率,使用其他采样率需要修改初始化代码"); return; } var sp=this.wsStreamPlay; if(!sp || !sp.__isStart) return; //if(播放新的) sp.clearInput(); //清除已输入但还未播放的数据,一般用于非实时模式打断老的播放 sp.input(pcm); } //初始化播放器 ,initStreamPlay:function(){ if(this.spIsInit)return; //已初始化完成 if(this.spInit_time && Date.now()-this.spInit_time<2000)return; //等待播放器初始化完成 var stime=this.spInit_time=Date.now(); var Tag="wsStreamPlay"; var sp=Recorder.BufferStreamPlayer({ decode:false,sampleRate:16000 //,realtime:false //默认为true实时模式,设为false为非实时模式。要连续完整播放时要设为false,否则实时模式会丢弃延迟过大的数据并加速播放 ,onInputError:function(errMsg, inputIndex){ console.error(Tag+"第"+inputIndex+"次的音频片段input输入出错: "+errMsg); } ,onPlayEnd:function(){ // 没有可播放的数据了,缓冲中 或者 已播放完成 } }); sp.start((function(){ if(stime!=this.spInit_time) return; //可能调用了destroy sp.__isStart=true; this.wsStreamPlay=sp; this.spIsInit=true; this.spInit_time=0; this.log("streamPlay已打开",2); }).bind(this),(function(err){ if(stime!=this.spInit_time) return; //可能调用了destroy this.spInit_time=0; this.log("streamPlay初始化错误:"+err,1); }).bind(this)); } //销毁播放器 ,destroyStreamPlay(){ this.spIsInit=false; this.spInit_time=0; if(this.wsStreamPlay){ this.wsStreamPlay.stop(); this.wsStreamPlay=null; } } }; window.RtVoiceObj=new PageObject(); /***显示界面***/ var isShow=false; window.rtVoiceOnShow=function(){ if(isShow) return; isShow=true; $(".rtVoiceView").html('\
\
\ WebSocket语音通话聊天\ 源码: /assets/zdemo.index.realtime_voice.js\
\ \
\
ws(s):
\
需要先在电脑上运行Recorder仓库\ /assets/node-localServer\ 内的nodejs服务器端脚本,然后填写你电脑局域网ip即可测试(用127.0.0.1时可用ws),支持ws、wss测试WebSocket地址
\ \
\ 我的标识\ \ \ \
\
\ \
\
服务器将pcm片段实时发送给客户端,模拟播放语音流
\ \ \ \
\
\ \
\
语音通话聊天对讲,请在上面进行录音操作,音频数据会实时传送给对方播放(实时pcm)
\ 对方标识\ \ \ \
\
\
\ \
\ \
\
发语音和文本消息(简单演示,需先连接)
\
\
\
按住发语音
\ \ \
\ \
\
清屏
\
\
\
\
\ '); var wsApi=localStorage["Rec_RtVoice_wsApi"]||"ws://127.0.0.1:9529/ws123"; $(".rtVoice_input_ws").val(wsApi); $(".rtVoice_input_wsID1").val(localStorage["Rec_RtVoice_wsID1"]||1); $(".rtVoice_input_wsID2").val(localStorage["Rec_RtVoice_wsID2"]||2); bindTouch(); }; /***发语音和文本消息***/ //长按发语音 var rtvoiceStart=false; var bindTouch=function(){ DemoFragment.BindTouchButton("rtvoiceVoiceBtn" ,"按住发语音" ,"松开结束录音" ,{upBG:"#0b1",downBG:"#fa0"} ,function(cancel){//按下回调 rtvoiceStart=true; //开始录音 var openFn=window.recreq||recopen; var errEnd=function(err){ if(err){ rtvoiceStart=false; rtmsgView("[错误]"+err,false); cancel("录音错误"); return; }; }; openFn(function(err){ if(err) errEnd(err); else recstart(errEnd); }); } ,function(isCancel,isUser){//结束长按回调 if(rtvoiceStart && !isCancel){ //结束录音 recstop(function(err,data){ rtvoiceStart=false; if(!isUser){ rtmsgView("[事件]touch事件被打断",false); return; }; if(err){ rtmsgView("[错误]"+err,false); return; }; RtVoiceObj.rtvoiceMsgSend(data.data, data.duration); }); }; } ); }; var rtmsgTime=function(){ var d=new Date(); return ''+("0"+d.getMinutes()).substr(-2)+"′"+("0"+d.getSeconds()).substr(-2)+" "; }; var rtmsgView=function(msg,isIn){ var id=RandomKey(16); $(".rtvoiceMsgBox").prepend('
'+rtmsgTime()+msg.replace(/[<>&]/g,function(a){return "&#"+a.charCodeAt(0)+";"}).replace(/ /g," ").replace(/[\r\n]/g,"
")+'
'); return id; }; var rtvoiceView=function(data,isIn){ var id=RandomKey(16); rtvoiceDatas[id]=data; $(".rtvoiceMsgBox").prepend('
'+rtmsgTime()+'语音 '+(data.duration/1000).toFixed(2)+'s
'); return id; }; var rtvoiceDatas={}; RtVoiceObj.rtvoicePlay=function(id){ var audio=$(".recPlay")[0]; audio.style.display="inline-block"; if(!(audio.ended || audio.paused)){ audio.pause(); }; audio.src=(window.URL||webkitURL).createObjectURL(rtvoiceDatas[id].data); audio.play(); }; //发送语音消息给对方 RtVoiceObj.rtvoiceMsgSend=function(blob,duration){ var msgId=rtvoiceView({data:blob, duration:duration},false); $("."+msgId+"_s").html("发送中"); var revCount=0; var msgEnd=function(err){ if(err){ $("."+msgId+"_s").html("发送失败"); rtmsgView("[语音未发送]"+err,false); return; } $("."+msgId+"_s").html("已发"+revCount+"人"); }; var err=this.readWsID2(); if(err) return msgEnd(err); var reader = new FileReader(); reader.onloadend = (function() { var bytes=new Uint8Array(reader.result); this.ws__sendMessage("sendTo",{ toMetaKey:"uid",toMetaValue:this.wsID2 ,sendType:"custom_voiceMsg",sendData:{ fromMetaKey:"uid",fromMetaValue:this.wsID1//告诉对方是我发的信息 ,mime:blob.type, duration:duration } },bytes,null,(function(data){ revCount=data.count; if(!data.count){//没有接收方 msgEnd("对方"+this.wsID2+"不在线"); return; }; msgEnd(""); }).bind(this),(function(err){ msgEnd(err); }).bind(this)); }).bind(this); reader.readAsArrayBuffer(blob); }; //收到对方发来的语音消息 RtVoiceObj.onMsg__custom_voiceMsg=function(data,binary,msgObj){ rtvoiceView({data:new Blob([binary], {type:data.mime}), duration:data.duration},true); }; //发送文本消息 RtVoiceObj.rtTxtMsgSend=function(txt){ var input=$(".rtvoiceMsgInput"); txt=txt||input.val(); var msgId=rtmsgView(txt,false); $("."+msgId+"_s").html("发送中"); var revCount=0; var msgEnd=function(err){ if(err){ $("."+msgId+"_s").html("发送失败"); rtmsgView("[消息未发送]"+err,false); return; } $("."+msgId+"_s").html("已发"+revCount+"人"); }; var err=this.readWsID2(); if(err) return msgEnd(err); this.ws__sendMessage("sendTo",{ toMetaKey:"uid",toMetaValue:this.wsID2 ,sendType:"custom_txtMsg",sendData:{ fromMetaKey:"uid",fromMetaValue:this.wsID1//告诉对方是我发的信息 ,txt:txt } },null,null,(function(data){ revCount=data.count; if(!data.count){//没有接收方 msgEnd("对方"+this.wsID2+"不在线"); return; }; input.val(""); msgEnd(""); }).bind(this),(function(err){ msgEnd(err); }).bind(this)); }; //收到对方发来的文本消息 RtVoiceObj.onMsg__custom_txtMsg=function(data,binary,msgObj){ rtmsgView(data.txt,true); }; })();