简单的直播系统

信令服务器的搭建

// 客户端的信令消息
* join 加入房间
* leave 离开房间
* message 端到端消息* Offer 消息 (获得本机的描述信息 SDP形式)* Answer 消息* Candidate 消息
// 服务端的信令消息
* joined 已加入房间
* otherjoin 其他用户加入房间
* full 房间人数已经满
* leaved 已经离开房间
* bye 告知对方离开房间

 // 信令服务器的搭建流程// 1.重新编写server.js文件// 2.编写 html css js 文件
// 信令服务器的搭建
'use strict'
var http = require('http') ;
var https = require('https') ;
var fs = require('fs') ; var express = require('express') ;
var serverIndex = require('serve-index') ; // socket.io 包的引入
var socketIo = require('socket.io') ;
// 指定直播的最大人数(不包括这个数字)
var USERCOUNT = 3 ;
// 日志文件的包的引入
var log4js  = require('log4js') ;
// log4j的配置和启动
log4js.configure({appenders: {file: {type: 'file',filename: 'app.log',layout: {type: 'pattern',pattern: '%r %p - %m',}}},categories: {default: {appenders: ['file'],level: 'debug'}}
});var logger = log4js.getLogger();var app = express() ;
app.use(serverIndex('./public')) ;
app.use(express.static('./public')) ; // http server
var http_server = http.createServer(app) ;
http_server.listen(80,'0.0.0.0') ; // certificate  configuration
var options = {//  这儿要填写自己的安全证书key:fs.readFileSync('./cert/cert.key'), //.代表是当前目录cert:fs.readFileSync('./cert/cert.pem')
}
// https server
var https_server = https.createServer(options,app) ;
// bind socket.io with https_server
var io = socketIo.listen(https_server) ; // connection function
io.sockets.on('connection',(socket)=>{// 服务器接收到消息后的处理socket.on('message',(room,data)=>{// 将消息转发为除房间内的除自己以外的所有人console.log('I come in server message') ; // 用来做测试使用socket.to(room).emit('message',room,data) ; }) ; // 加入房间消息socket.on('join',(room)=>{// 服务端接收到加入房间的消息后,直接加入房间socket.join(room) ; // 获得当前的房间号var myRoom = io.sockets.adapter.rooms[room];// 获得当前房间的人数var users = (myRoom)?Object.keys(myRoom.sockets).length:0; // 输出当前直播间的人数logger.debug('the user number of room is:' + users) ; //如果还没有达到最大容量,这可以将用户成功加入if(users < USERCOUNT){// 向客户端发送加入成功socket.emit('joined',room,socket.id) ; // 如果当前直播间还有其他人,则告诉其他人,自己加入成功if(users >1 ){socket.to(room).emit('otherjoin',room,socket.id) ; }}else{// 直播间已经满了socket.leave(room) ; // 告知客户端直播间已经满了socket.emit('full',room,socket.id) ; }// 发送信息告知 ,客户端已经发送成功// socket.emit('joined',room,socket.id) ;}) ; // 离开直播间操作socket.on('leave',(room)=>{//  获得当前直播间的号码var myRoom = io.sockets.adapter.rooms[room];// 获得当前直播间的人数var users = (myRoom)?Object.keys(myRoom.sockets).length:0 ; logger.debug('the user number of room is:'+(users - 1)) ; // 通知直播间的其他用户,我走了socket.to(room).emit('bye',room,socket.id) ; // 告知客户端 离开socket.emit('leaved',room,socket.id) ; }) ;
}) ;
// bind port
https_server.listen(443,'0.0.0.0') ;
/*main.css 的编写*/
/**  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.**  Use of this source code is governed by a BSD-style license*  that can be found in the LICENSE file in the root of the source*  tree.*/button {margin: 10px 20px 25px 0;vertical-align: top;width: 134px;
}table {margin: 200px (50% - 100) 0 0;
}textarea {color: #444;font-size: 0.9em;font-weight: 300;height: 20.0em;padding: 5px;width: calc(100% - 10px);
}div#getUserMedia {padding: 0 0 8px 0;
}div.input {display: inline-block;margin: 0 4px 0 0;vertical-align: top;width: 310px;
}div.input > div {margin: 0 0 20px 0;vertical-align: top;
}div.output {background-color: #eee;display: inline-block;font-family: 'Inconsolata', 'Courier New', monospace;font-size: 0.9em;padding: 10px 10px 10px 25px;position: relative;top: 10px;white-space: pre;width: 270px;
}div#preview {border-bottom: 1px solid #eee;margin: 0 0 1em 0;padding: 0 0 0.5em 0;
}div#preview > div {display: inline-block;vertical-align: top;width: calc(50% - 12px);
}section#statistics div {display: inline-block;font-family: 'Inconsolata', 'Courier New', monospace;vertical-align: top;width: 308px;
}section#statistics div#senderStats {margin: 0 20px 0 0;
}section#constraints > div {margin: 0 0 20px 0;
}h2 {margin: 0 0 1em 0;
}section#constraints label {display: inline-block;width: 156px;
}section {margin: 0 0 20px 0;padding: 0 0 15px 0;
}video {background: #222;margin: 0 0 0 0;--width: 100%;width: var(--width);height: 225px;
}@media screen and (max-width: 720px) {button {font-weight: 500;height: 56px;line-height: 1.3em;width: 90px;}div#getUserMedia {padding: 0 0 40px 0;}section#statistics div {width: calc(50% - 14px);}}
// js的编写
'use strict'var localVideo = document.querySelector('video#localvideo') ;
var remoteVideo = document.querySelector('video#remotevideo') ; var btnConn = document.querySelector('button#connserver') ;
var btnLeave = document.querySelector('button#leave') ; // 初始化一些数据
var localStream = null ;
var  pc = null  ;
var roomid;
var socket = null;
var state = 'init'  ;
//  将房间号设置为全局变量
var roomid = '123456' ; //处理错误函数
function handleError(err){console.error('fail to',err) ;
}function sendMessage(roomid,data){console.log('send p2p message',roomid,data) ; //  然后实现端到端的通信console.log(socket) ; if(socket){console.log(1111) ; socket.emit('message',roomid,data) ; }}function handelOfferError(err){console.error('Failed to get Offer',err) ;
}function getOffer(desc){// 触发本端来收集candidate pc.setLocalDescription(desc) ; //  给另一端发送一个端对端的消息sendMessage(roomid,desc) ;
}function handleAnswerError(err){console.error('Failed to get Answer!',err) ;
}function getAnswer(desc){pc.setLocalDescription(desc) ; sendMessage(roomid,desc) ;
}function call(){// 只有 处于以下状态 下,才能进行一下操作if(state === 'joined_conn'){if(pc){var options = {offerToReceiveAudio:1,offerToReceiveVideo:1}pc.createOffer(options).then(getOffer).catch(handelOfferError); }}
}function conn(){// 1. 让其与 scoketio 进行连接 // 成功与信令服务器建立了连接socket = io.connect() ; // 注册消息socket.on('joined',(roomid,id) =>{btnConn.disabled = true;btnLeave.disabled = false;// 改变客户端状态state = 'joined' ; // 加入房间 后开始创建PeerConnectioncreatePeerConnection() ; console.log('receive join message:state=',state) ; }) ; socket.on('otherjoin',(roomid,id) =>{console.log('receive otherjoin message:',roomid,id) ; // 对 某一用户离开后,另一用户又进来的时,// 需要重新创建PeerConnectionif(state === 'joined_unbind'){createPeerConnection() ; }state = 'joined_conn' ; // 媒体协商call() ;//console.log('receive otherjoin message:state',state) ;}) ; socket.on('full',(roomid,id) =>{console.log('receive full message:',roomid,id) ; state = 'leaved' ; console.log('receive full message:state',state) ;//  将用户连接断掉socket.disconnect() ; //  显示 窗口 给用户一个提示alert('the room is full') ; // 设置按钮的状态btnConn.disabled = false ; btnLeave.disabled = true ;}) ; socket.on('leaved',(roomid,id) =>{console.log('receive leaved message:',roomid,id) ; state = 'leaved' ;console.log('receive leaved message:state',state) ;//  将用户连接断掉socket.disconnect() ; // 设置按钮的状态btnConn.disabled = false ; btnLeave.disabled = true ;}) ; socket.on('bye',(roomid,id) =>{console.log('receive bye message:',roomid,id) ; state = 'joined_unbind' ; closePeerConnection() ; console.log('receive bye message:state',state) ; }) ;//  这儿处理端到端的消息 ,媒体协商socket.on('message',(roomid,data)=>{console.log('receive client message:',roomid,data) ; // 媒体协商// 首先 判断传过来的数据是否是正确的if(data){if(data.type === 'offer'){//  经过emit转发过来后,此时由对象变成了文本了。pc.setRemoteDescription(new RTCSessionDescription(data)) ;pc.createAnswer().then(getAnswer).catch(handleAnswerError) ; }else if(data.type === 'answer'){pc.setRemoteDescription(new RTCSessionDescription(data)) ;}else if(data.type === 'candidate'){var candidate = new RTCIceCandidate({sdpMlineIndex:data.sdpMLineIndex,candidate:data.candidate,sdpMid:data.id}) ; pc.addIceCandidate(candidate) ; }else{console.error('the message is invalid',data) ; }}}) ; // 发送消息,此处固定加入的房间为 123456socket.emit('join','123456') ; return ; }// 实现 leave 操作
function leave(){if(socket){socket.emit('leave', '123456') ; }//  释放资源closePeerConnection() ; closeLocalMedia() ; btnConn.disabled = false ;btnLeave.disabled = true ;
}// 获取到视频流的操作
function getMediaStream(stream){localStream = stream;//本地视频展现localVideo.srcObject = localStream ; // 一定要先显示了视频,再创创建连接//  与scoketio 进行连接 ,并且接收消息conn() ;
}//开启本地视频函数
function start(){// 这段代码在前几次学习时已经写了许多次了if(!navigator.mediaDevices ||!navigator.mediaDevices.getUserMedia){console.error('the getUserMedia is not support') ; }else {var constraints = {video:true ,audio:true  }navigator.mediaDevices.getUserMedia(constraints).then(getMediaStream).catch(handleError) ; }}// 连接信令服务器方法
function connSignalServer(){// 开启本地视频start() ; return true ;
}
// 创建 peerconnection
function createPeerConnection(){console.log('create RTCPeerConection') ; // 判断一下 peerconnection 是否已经存在了 ,如果不存在再创建if(!pc){//  这儿是自己配置好的stun /turn 服务 信息var pcConfig = {'iceServers':[{'urls':'turn:39.107.40.232:3478','credential':'lch1234','username':'lch'}]}pc = new RTCPeerConnection(pcConfig) ; //  协商时  ,执行下列事件 pc.onicecandidate =(e)=>{if(e.candidate){// 向对端发送消息console.log('find an new candidate',e.candidate) ; sendMessage(roomid,{type:'candidate',label:e.candidate.sdpMLineIndex,id:e.candidate.sdpMid,candidate:e.candidate.candidate}) ;}}// 将流送给远端pc.ontrack = (e)=>{console.log('wo zhi xing')// e.streams[0] 代表许多流中的第一个流console.log(e.streams[0]) ; remoteVideo.srcObject  = e.streams[0] ; }}// 如果接收到本地流的话,就遍历本地流if(localStream){localStream.getTracks().forEach((track)=>{console.log('2222') ; console.log('find',track) ; pc.addTrack(track,localStream) ;}) ;}}
// 关闭本地媒体流
function closeLocalMedia(){if(localStream && localStream.getTracks()){localStream.getTracks().forEach((track)=>{track.stop() ; }) ; }localStream = null;
}// 销毁掉peerconnection
function closePeerConnection(){console.log('colse RTCPeerConnection!') ; if(pc){// 销毁掉peerconnectionpc.close() ; pc = null ; }
}
btnConn.onclick = connSignalServer ;
btnLeave.onclick = leave ;

效果图:

WebRTC学习06----1对1视频通信实例相关推荐

  1. 音视频通信为什么要选择WebRTC?

    在网上经常看到有人说:"在线教育直播是用WebRTC做的","音视频会议是用WebRTC做的"--:"声网.腾讯.阿里--都使用的WebRTC&quo ...

  2. 译:WebRTC视频通信浅析

    译:WebRTC视频通信 http://blog.csdn.net/csdnhaoren13/article/details/50999168 原文:http://www.html5rocks.com ...

  3. 译:WebRTC视频通信

    原文:http://www.html5rocks.com/en/tutorials/webrtc/infrastructure/ WebRTC可以进行p2p之间的通信,但是仍需要服务支持. 1. si ...

  4. 视频教程-Android WebRTC 实现1V1实时音视频通信-Android

    Android WebRTC 实现1V1实时音视频通信 从2012年开始从事移动互联网方面的开发工作,曾担任去哪儿网开发工程师,搜狗高级开发工程师,拥有多年一线实战开发经验. 擅长语言:Object- ...

  5. webrtc学习记录三【创建基于RTCPeerConnection本机内的1v1音视频互通】

    系列文章目录 webrtc学习记录一[媒体录制MediaRecorder] webrtc学习记录二[基于socket.io创建信令服务器聊天室] 目录 系列文章目录 前言 一.媒体能力的协商过程 1. ...

  6. android webrtc学习五(webrtc视频数据传递和切换摄像头问题处理)

    android webrtc学习五(webrtc视频数据传递和切换摄像头问题处理) Android webrtc摄像头流程分析 1.打开摄像头 2.获取流数据 摄像头切换 问题场景:在使用华为手机(忘 ...

  7. WebRTC学习笔记

    http://blog.chinaunix.net/uid-24567872-id-3961702.html 1.     WebRTC学习 1.1   WebRTC现状 本人最早接触WebRTC是在 ...

  8. 进入全真互联网——音视频通信的技术变革

    导 语 随着5G和下一代编解码.传输等技术持续演进,音视频通话技术从低延时到超低延时实时通信快速迭代,越来越多应用与服务迁移至线上,越来越真实无损地还原线下体验,新的互动方式和场景不断涌现,从高度数字 ...

  9. 【免费活动】解析腾讯云音视频通信三大核心网络技术实战与创新

    随着互联网的发展越来越成熟,移动终端成为我们人手必备的生活用品,云计算的普及与高速发展,4G.5G网络的瓜熟蒂落,我们真正的进入了全真互联网时代.2020年,一场突如其来的疫情,很多传统行业不得不将线 ...

最新文章

  1. DS博客作业08--课程总结
  2. 本地开发环境与生产环境布局有偏差问题
  3. sqli-labs less11 POST注入-字符型
  4. bms_output.put_line使用方法
  5. Java的Socket编程实例
  6. 用ps制作计算机系海报,如何利用素材制作海报_ps海报制作_ps教学_课课家
  7. 遗传算法实践详解(deap框架初体验)
  8. 三相异步电动机的公式
  9. 咱就是说,方言配音的软件能有多少
  10. 【win10蓝屏】记录一下,随机蓝屏,开机蓝屏,使用中蓝屏的经历
  11. 推流地址 java_如何通过代码生成推流地址和播放地址?
  12. php微信支付需要哪些设置,如何申请和配置微信支付接口?
  13. Lotus Notes Send EMail from VB or VBA
  14. 微软工程院 硕士_微软工程院招聘NLP算法研究员实习生|NLP算法工程师实习生_北京实习招聘...
  15. html5 canvas图片缩放,拖拽
  16. 阿里巴巴淘宝全链路性能优化(上)
  17. 年产10000吨餐厨垃圾制备氨基酸有机肥工厂设计
  18. 高等数学中必须掌握的基础知识(一)
  19. 你还在为看电影发愁?Python制作全网视频播放工具!
  20. P72 检查约束与默认值约束

热门文章

  1. 银河麒麟使用时遇到的问题
  2. 【VNC使用指南】Ubuntu Kylin 使用 TigerVNC
  3. 【操作系统】I/O系统
  4. PhotoShop彩色图片打印机只有四中颜色操作步骤:
  5. 前端工程化实践总结 | QQ音乐商业化Web团队
  6. 好一座假山!———初谈岳不群
  7. colorkey口红怎么样_COLORKEY口红最近太火!不到50块,颜色比大牌还美,尤其是这4支...
  8. GDUT 2.25 D
  9. html自动生成段落,HTML中的段落文本怎么换行
  10. php做引流脚本,自动引流脚本你知道是怎么实现的自动化引流?