licode安装过程及发布局域网服务 |
您所在的位置:网站首页 › licode设置码率 › licode安装过程及发布局域网服务 |
为什么80%的码农都做不了架构师?>>> 本系统是基于谷歌的webRTC浏览器端视频点对点技术实现网页视频及语音即时通讯。采用开源项目licode作为聊天室管理,以及其web端js模块。 目前系统采用前后端分离以及多服务器方式部署,采用nginx作为反向代理集成licode、apiserver、front。 环境要求: 操作系统:ubuntu14、nginx、nodejs、RibbitMQ Licode安装在ubuntu中安装: 先从GitHub上下载源码 git clone https://github.com/ging/licode.gitcd licode解决HTTPS/SSL访问错误: 由于是配置文件名不统一导致,而且在Sokect.js中的token.secure参数无法配置,故直接修改。 /licode/erizo_controller/erizoClient/src/Socket.js 将licode_default.js文件修改SSL相关配置,注意ssl证书也需要修改 ssl相关设置为true; 然后复制licode_default.js为licode_config.js
获得源码后进入licode/scripts/目录,复制licode_default.js为licode_config.js,同时将licode_default.js和licode_config.js都修改为如下配置: // 配置turnserver服务器 config.erizoController.iceServers = [{'url': 'stun:stun.l.google.com:19302'},{'url': 'stun:服务器IP:3478'}];//注意,配置的服务器必须是可访问的,否则启动失败 // 开启 SSL config.erizoController.ssl = true; config.erizoController.listen_ssl = true; //default value: false config.erizoController.listen_port = 8080; //default value: 8080
// 配置SSL 文件 config.erizoController.ssl_key = '/full/path/to/ssl.key'; config.erizoController.ssl_cert = '/full/path/to/ssl.crt';
依次序执行安装: 1、安装系统依赖 ./scripts/installUbuntuDeps.sh
2、安装Erizo和Nuve ./scripts/installErizo.sh./scripts/installNuve.sh3、安装基本实例程序 ./scripts/installBasicExample.sh4、运行licode ./licode/scripts/initLicode.sh5、运行基础实例web服务 ./scripts/initBasicExample.shLicode API 官方地址: http://licode.readthedocs.io/en/master/client_api/ 包含 客户端API 、服务端API 具体详见官网。
turnserver安装 在使用WebRTC进行即时通讯时,需要使浏览器进行P2P通讯,但是由于NAT环境的复杂性,并不是所有情况下都能进行P2P,这时需要TURN Server来帮助客户端之间转发数据。rfc5766-turn-server是一个高性能的开源TURN Server实现。
以下是在EC2上使用Ubuntu操作系统安装rfc5766-turn-server: 1. 下载安装包: $ wget http://ftp.cn.debian.org/debian/pool/main/r/rfc5766-turn-server/rfc5766-turn-server_3.2.4.4-1_amd64.deb 2. 安装: $ sudo apt-get update $ sudo apt-get install gdebi-core $ sudo gdebi rfc5766-turn-server_3.2.4.4-1_amd64.deb 安装完后,在/usr/share/doc/rfc5766-turn-server下有很多文档可参考。 3. 配置: $ sudo vi /etc/turnserver.conf --------------------------------------- // 配置IP,EC2下需要配置listening-ip和external-ip listening-ip=172.31.4.37 注意:如果是未知IP,可设置0.0.0.0 external-ip=54.223.149.60 // 当TURN Server用于WebRTC时,必须使用long-term credential mechanism lt-cred-mech // 增加一个用户 user=username1:password1 // 设定realm realm=mycompany.org ---------------------------------------
4. 启动: sudo turnserver -c /etc/turnserver.conf --daemon
5. 服务启动后,在上一个WebRTC示例中更改iceServers后测试: "iceServers": [{ "url": "stun:stun.l.google.com:19302" }, { "url": "turn:54.223.149.60", "username": "username1", "credential": "password1" }]
更多安装信息在:http://turnserver.open-sys.org/downloads/v3.2.4.4/INSTALL
rfc5766-turn-server当然也有STUN Server的能力,但是需要给它配置2个IP,以帮助探测客户端所在NAT环境的行为,这里没有做。
前端集成 时序图 WebRTC相关时序: 浏览器通过candidate 来进行连接。连接之后,就无需与服务器进行连接。 在获得candidate时需要连接turnserver。具体是由浏览器webRTC模块执行。
Licode集成时序图:
Nginx反向代理配置 运行./scripts/initLicode.sh 和./scripts/initBasicExample.sh Licode服务器提供 http端口 3000 3001 websocket 端口8080 https 端口 3004 websocket 端口 8080 nginx配置如下: 前端程序nginx配置: server { listen 443 ssl; ssl_certificate /usr/local/etc/nginx/server.crt; ssl_certificate_key /usr/local/etc/nginx/server.key;
# ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5; # ssl_prefer_server_ciphers on; server_name '192.168.10.225'; if ($scheme = http) { return 301 https://$server_name$request_uri; } location / { root html; #前端静态运行目录 index index.html index.htm; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8085/; } location /api{ #接口服务 proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8080/api; } #文件上传后存放目录 location /upload{ proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8080/upload; } #licode 创建token路径转发 location /createToken{ proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://192.168.10.228:3001/createToken; } }
代码示例 Websock服务器: io.on('connect', function (socket) { socket.on('message', function (data) { var data = JSON.parse(data); console.log("请求数据", data); //当前登录用户名 var userId = data.from.id; //当前用户的聊天室id var roomId = data.roomId; socket.userId = userId; socket.roomId = roomId; function join(f) { //当用户与聊天室id存在时 if (allUsers[roomId] == undefined) { allUsers[roomId] = {}; } if (allSockets[roomId] == undefined) { allSockets[roomId] = {}; } //保存用户信息 allUsers[roomId][userId] = data.from; if (allSockets[roomId][userId] != undefined && allSockets[roomId][userId].scoket != undefined && f == true) {//给之前的连接发送退出信息 sendTo(allSockets[roomId][userId].scoket,{ event: "leave" }); } allSockets[roomId][userId] = {'scoket': socket}; } try { switch (data.event) { case "heartbeat": join(false); break; //当有新用户加入时 case "join": join(true); showUserInfo(allUsers[roomId]); sendTo(socket, { event: "join", "message": "成功加入聊天室", "success": true }); break; //======== 即时通讯 IM ========= case "leave": delete allUsers[roomId][userId]; showUserInfo(allUsers[roomId]); break; case "msg"://IM消息 join(); if (data.message == undefined || data.message == '' || data.message == null) { break; } //获取room用户 var roomId = data.roomId; var ulist = allUsers[roomId]; data.datetime = new Date(); if (ulist != undefined) { //查找用户连接发送信息 for (var u in ulist) { if (ulist[u]) sendTo(allSockets[roomId][u].scoket, data); } } break; case "historyMsg"://聊天历史 join(); //获取room用户 var roomId = data.roomId; var ulist = allUsers[roomId]; break //======== 基于allWebsockets start ========= case "sigin"://登录系统 for (var ws in allWebsockets) { if (allWebsockets[ws]) sendTo(allWebsockets[ws], { event: "sigin", "message": "登录", "success": true, from: data.from }); } socket.mainId=data.from.id; socket.userId=data.from.id; allWebsockets[data.from.id] = socket; break; case "joinChatRoom"://要求加入视频会议 var userList = data.userList; if (userList) { for (var i = 0; i < userList.length; i++) { if (allWebsockets[userList[i]]) sendTo(allWebsockets[userList[i]], { event: "joinChatRoom", "message": data.message || "加入会议", from: data.from, roomId: data.roomId }); } } allWebsockets[data.from.id] = socket; break; case "rejectJoinChatRoom"://拒绝加入视频会议 sendTo(allWebsockets[data.to], { event: "rejectJoinChatRoom", "message": data.message || "加入会议", from: data.from, roomId: data.roomId }); break; //================================= } } catch (ex) { console.log("异常:\t", ex) } }); socket.on("disconnect", function () { if (true) { return; } var userId_ = socket.userId; var roomId_ = socket.roomId; if (userId_ != undefined && userId_ != null && userId_ != '') { if (roomId_||allUsers[roomId_] == undefined || allSockets[roomId_] == undefined) { return; } var ulist = allUsers[roomId_]; if (ulist != undefined) { //查找用户连接发送信息 for (var u in ulist) { if (allSockets[roomId_][u]) sendTo(allSockets[roomId_][u].scoket, {event: 'leave', userId: userId_}); } } //删除已经退出的用户 delete allUsers[roomId_][userId_]; if(socket.mainId) delete allWebsockets[socket.mainId]; if (allUsers[roomId_] != undefined) { delete allSockets[roomId_][userId_]; showUserInfo(allUsers[roomId_]); } } }); }); function showUserInfo(allUsers) { console.log("在线用户:", allUsers); sendTo(io, { event: "showUserList", "userList": allUsers, }); } function removeUserInfo(roomId, userId) { delete allUsers[roomId][userId]; delete allSockets[roomId][userId]; } function sendTo(connection, message) { console.log("send data:\t", message); if (connection != undefined) connection.send(message); }
启动websoket服务器 : node server.js 以上包含两种websoket处理,如文本聊天处理、视频会议邀请处理。
前端集成: 在/front/src/views/Main.vue中定义一个主要的websoket连接,来处理视频会议邀请。 需要在index.html引用socket.io.js 代码如下: export default { data() { return { currentUser: this.$store.getters.getCurrentLoginInfo.user, main_websocket:io.connect(Constants.wsUrl) }; }, methods: { send(message) { message.from = {id: this.currentUser.id, name: this.currentUser.name, avator: this.currentUser.avator}; this.main_websocket.send(JSON.stringify(message)); } }, created() { //监听websoket消息 this.main_websocket.on('message', (data) => { console.error("on message:", data); switch (data.event) { case "joinChatRoom": var noticeName='roomId'+data.roomId; this.$Notice.open({title:'视频会议邀请',name:noticeName,render:(r)=>{ return r('div',[r('div',data.from.name+'邀请您加入视频会议,是否接受?'), r('Button',{ on: { click:()=>{ this.$router.push("/chat/" + data.roomId); this.$Notice.close(noticeName); } },props:{type:'primary',size:'small'}},'接受'), r('Button',{ on: {click:()=>{ this.send({event:"rejectJoinChatRoom",to:data.from.id,roomId:data.roomId}); this.$Notice.close(noticeName); } },props:{type:'error',size:'small'}},'拒绝') ]); },duration:10,onClose:()=>{ }}); break; case "sigin": break; case "rejectJoinChatRoom": this.$Notice.warning({title:'拒绝视频会议',desc:data.from.name+'拒绝加入视频会议。',duration:10}); break; case "message": this.$Notice.info({title:data.title|'消息',desc:data.message,duration:10}); break; } }); this.send({event:"sigin"}); } };视频会议邀约: 对应vuejs文件:/front/src/views/chat/index.vue 步骤代码如下: import http from '../../api/http' import Room from './licodeRoom.vue' export default { data() { return { roomShow: false, hospno: null, patient:null, roomId: null, doctorList: [], tableHeight: window.innerHeight - 80 } }, methods: { chosePatient() { http.post(this,'/pt/patient/choseOne',{hospno:this.hospno},(resp)=>{ var ret=resp.body; if(ret.code=='111'){ this.patient=ret.value; }else{ this.$Message.error(ret.msg); } }); }, joinRoom() { this.roomId = this.hospno; this.$router.push("/chat/"+this.roomId); this.$parent.send({event:'joinChatRoom',roomId:this.roomId,userList:this.doctorList}); }, closeDiv() { this.roomShow = false; this.hospno = null; this.roomId = null; }, loadRouter(){ var params=this.$route.params; console.error(params); if(params.roomId!=''&¶ms.roomId!='-'&¶ms.roomId!='null'&¶ms.roomId!='undefined'){ this.roomId=params.roomId; this.roomShow = true; } } }, components: { Room }, watch:{ '$route'(val){ #监听路由变化' if(val&&val!='-'){ #如果路由参数存在且不为‘-’ 则加载路由,并弹出licodeRoom.vue的界面 加入视频会议 this.loadRouter(); } } }, mounted() { this.loadRouter(); }, created() { } }
licodeRoom.vue代码: 代码文件:/front/src/views/chat/licodeRoom.vue //设置视频位置 var fixedTop = function () { //将已有的视频换到主视频模式 var els = document.getElementsByClassName('main_video'); //小窗口排列 var el_list = document.getElementsByClassName("video"); console.error("视频数量==》" + el_list.length); if (els== undefined||els == null ||els.length== 0){ el_list[0].style.top="0px"; el_list[0].className = 'main_video'; } for (var i = 0; i < el_list.length; i++) { var el = el_list[i]; el.style.top = (i * 80) + 'px'; var p=el.querySelector(".licode_player"); if(p) { p.ondblclick = function () { document.getElementsByClassName("main_video")[0].className = 'video'; var pr = this.parentNode.parentNode; pr.className = 'main_video'; pr.style.top = "0px"; console.error(this.parentNode.id); fixedTop(); } } } }//对应 export default 的methods的方法 //发布视频流 subscribeToStreams(streams) { var cb = function (evt) { console.error('Bandwidth Alert', evt.msg, evt.bandwidth); }; for (var index in streams) { var stream = streams[index]; if (this.localStream.getID() !== stream.getID()) { this.room.subscribe(stream, {metadata: {type: 'subscriber'}}); stream.addEventListener('bandwidth-alert', cb); } } }, initLicode() { this.localStream = Erizo.Stream({ audio: true, video: true, data: true, attributes: {id: this.currentUser.id, name: this.currentUser.name}, desktopStreamId: this.currentUser.id, videoSize: [640, 480, 1920, 1080]//,videoFrameRate: [10, 20] }); axios.defaults.headers.post['Content-Type'] = 'application/json'; axios.defaults.headers.post['Accept'] = '*/*'; axios.post(Constants.licodeServer + 'createToken/', { room: this.id, username: encodeURI(this.currentUser.name), role: 'presenter' }).then((resp) => { var token = resp.data; this.room = Erizo.Room({token: token}); this.room.addEventListener('room-connected', (event) => { this.room.publish(this.localStream, {maxVideoBW: 2000, minVideoBW: 1000,metadata: {type: 'publisher'}}); console.error("this.localStream room-connected id\t" + this.localStream.getID()); this.subscribeToStreams(event.streams); }); this.room.addEventListener('stream-subscribed', (streamEvent) => { var stream = streamEvent.stream; var attrs = stream.getAttributes(); console.error("stream-subscribed\t接收视频流 id\t" + attrs.id); if (attrs.id == this.currentUser.id) return; var s = this.$el.querySelector(".main_video"); if (s != undefined && s != null) s.className = "video"; var elem = document.createElement("div"); elem.setAttribute("id", attrs.id); elem.setAttribute("title", attrs.name); elem.className = "main_video"; this.$el.querySelector("#video_list").appendChild(elem); var videoEl = document.createElement("div"); videoEl.className = "videoPlay"; videoEl.setAttribute("id", "video_" + attrs.id); elem.appendChild(videoEl); stream.show("video_" + attrs.id); // document.getElementById("audio_login").play(); //移除licode logo document.getElementById("video_" + attrs.id).getElementsByClassName('licode_link')[0].innerHTML=''; fixedTop(); }); this.room.addEventListener('stream-added', (event) => { console.error("stream-added id\t" + event.stream.getID()); var streams = []; streams.push(event.stream); this.subscribeToStreams(streams); }); this.room.addEventListener('stream-removed', (streamEvent) => { console.error("stream-removed"); // Remove stream from DOM var stream = streamEvent.stream; if (stream.getID() !== undefined) { var attrs = stream.getAttributes(); var element = document.getElementById(attrs.id); if (element != undefined && element != null) this.$el.querySelector("#video_list").removeChild(element); fixedTop(); } }); this.room.addEventListener('stream-failed', () => { console.error("stream-failed"); this.room.disconnect(); }); this.localStream.addEventListener('access-accepted', () => { console.error("this.localStream access-accepted"); this.room.connect(); var attrs = this.localStream.getAttributes(); var elem = document.createElement("div"); elem.setAttribute("id", attrs.id); elem.setAttribute("title", attrs.name); elem.className = "main_video"; this.$el.querySelector("#video_list").appendChild(elem); var videoEl = document.createElement("div"); videoEl.setAttribute("id", "video_" + attrs.id); videoEl.className = "videoPlay"; elem.appendChild(videoEl); this.localStream.show("video_" + attrs.id);//, {speaker: true}); //移除licode logo document.getElementById("video_" + attrs.id).getElementsByClassName('licode_link')[0].innerHTML=''; fixedTop(); }); this.localStream.init(); }); }, quitRoom() { if(this.localStream){}else{return;} this.localStream.stop(); //取消订阅视频 this.room.unsubscribe(this.localStream, (result, error) => { if (result === undefined) { console.error("Error unsubscribing", error); } else { console.error("Stream unsubscribed!"); } }); //取消发布视频 this.room.unpublish(this.localStream, (result, error) => { if (result === undefined) { console.error("Error unpublishing", error); } else { console.error("Stream unpublished!"); } }); this.send({event:'leave'}); this.room.disconnect(); this.room = null; this.localStream = null; }
会议聊天室说明: 初始化本地视频流 this.localStream = Erizo.Stream({ audio: true, video: true, data: true, attributes: {id: this.currentUser.id, name: this.currentUser.name}, desktopStreamId: this.currentUser.id, videoSize: [640, 480, 1920, 1080]//,videoFrameRate: [10, 20] });获取聊天室token在获取token成功后初始化room,对room设置事件监听: room-connected 聊天室连接成功 连接成功后发布本地视频流localStream stream-subscribed 视频流订阅成功将获得的视频流播放, 通过fixedTop()方法设置位置,及设置主视频窗口。 stream-added 视频流加入后进行整个room订阅视频流。并对此视频流设置bandwith-alert监听。stream-removed 视频流移除或断开监听对已经断开的视频流的video进行移除,并且调用fixedTop来重新布局设置主次屏。 stream-failed 视频连接失败监听room断开连接。 对本地视频流监听,access-accepeted:连接room:this.room.connect(); 播放本地视频流,并且调用fixedTop来重新布局设置主次屏。 初始化localStream。 this.localStream.init(); |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |