webRTC(十):webrtc 实现web端对端视频

  • 采集本地视频
function start(){
	
	
	if (!navigator.mediaDevices||
			!navigator.mediaDevices.getUserMedia) {			
		  console.log("getUserMedia is not supported!")
		  return;
	} else {
		
		//1 ===============配置音视频参数===============
		var constraints={
			video:true,
			audio: true
		}
		
		navigator.mediaDevices.getUserMedia(constraints)
							  .then(getMediaStream)
							  .catch(handleError)
	}
}

  • 扑捉本地视频
// 扑捉本地视频
function getMediaStream(stream){
	
	localStream =stream;
	//2 ===============显示本地视频===============
	localVideo.srcObject = localStream;
	
	//这个函数的调用时机特别重要 一定要在getMediaStream之后再调用,否则会出现绑定失败的情况
	conn();
}

  • 创建socket连接和监听

function conn(){
	//1 触发socke连接
	socket = io.connect();
	
	//2 加入房间后的回调
	socket.on('joined',(roomid,id)=>{
		
		state = 'joined';
		
		createPeerConnection();
		
		btnConn.disabled = true;
		btnLeave.disabled =false;
		
		console.log("reveive joined message:state=",state);	
	});
	socket.on('otherjoin',(roomid,id)=>{
		
		if (state === 'joined_unbind') {
			createPeerConnection();
		}
		state = 'joined_conn';
		
		//媒体协商
		call();
		console.log("reveive otherjoin message:state=",state);	
	});
	socket.on('full',(roomid,id)=>{
		console.log('receive full message ', roomid, id);

		closePeerConnection();
		closeLocalMedia();
		
		state = 'leaved';
		
		btnConn.disabled = false;
		btnLeave.disabled = true;
		console.log("reveive full message:state=",state);
		alert("the room is full!");
	});
	
	socket.on('leaved',(roomid,id)=>{
		
		state = 'leaved';
		socket.disconnect();
		btnConn.disabled = false;
		btnLeave.disabled = true;
		console.log("reveive leaved message:state=",state);
	});
	
	socket.on('bye',(roomid,id)=>{
		
		state = 'joined_unbind';
		closePeerConnection();
		console.log("reveive bye message:state=",state);	
	});
	socket.on('disconnect', (socket) => {
		console.log('receive disconnect message!', roomid);
		if(!(state === 'leaved')){
			closePeerConnection();
			closeLocalMedia();
		}
		state = 'leaved';
	
	});
	socket.on('message',(roomid,id,data)=>{
		
		//媒体协商
		if(data){
			if(data.type === 'offer'){
				pc.setRemoteDescription(new RTCSessionDescription(data));
				pc.createAnswer()
				  .then(getAnswer)
				  .catch(handleAnswerError);
			}else if(data.type === 'answer'){
				console.log("reveive client message=====>",data);
				pc.setRemoteDescription(new RTCSessionDescription(data));
			}else if(data.type === 'candidate'){
				var candidate = new RTCIceCandidate({
					sdpMLineIndex:data.label,
					candidate:data.candidate
				});
				pc.addIceCandidate(candidate);
				
			}else{
				console.error('the message is invalid!',data)
			}
		}
		
		console.log("reveive client message",roomid,id,data);	
	});
	
	//加入房间
	socket.emit('join',roomid);
	return;
}
  • 创建本地流媒体链接
//创建本地流媒体链接
function createPeerConnection(){
	console.log('create RTCPeerConnection!');
	if(!pc){
		pc = new RTCPeerConnection(pcConfig);
		pc.onicecandidate = (e) =>{
			if(e.candidate){
				sendMessage(roomid,{
					type:'candidate',
					label:e.candidate.sdpMLineIndex,
					id:e.candidate.sdpMid,
					candidate:e.candidate.candidate
				});
			}
		}
		pc.ontrack = (e)=>{
			remoteVideo.srcObject = e.streams[0];
		}
	}
	if(pc === null || pc === undefined){
			console.error('pc is null or undefined!');
			return;
	}

	if(localStream === null || localStream === undefined){
		console.error('localStream is null or undefined!');
		return;
	}

	if(localStream){
		localStream.getTracks().forEach((track)=>{
			pc.addTrack(track,localStream);
		})
	}
}

//使用turn穿越
var pcConfig={
		'iceServers':[{
			'urls':'turn:121.41.76.43:3478',
			'credential':'123456',
			'username':'huang'
		}]
	}
	
  • 接收远端流通道
//接收远端流通道
function call(){
	if(state === 'joined_conn'){
		if(pc){
			var options = {
				offerToReceiveAudio:1,
				offerToReceiveVideo:1
			}
			pc.createOffer(options)
			  .then(getOffer)
			  .catch(handleOfferError);
		}
	}	
}

  • 关闭本地媒体流链接
//关闭本地媒体流链接
function closePeerConnection(){
	console.log('close RTCPeerConnection!');
	if(pc){
		pc.close();
		pc = null;
	}
}

效果

在这里插入图片描述

源码

  • html
<html>
	<head>
		<title>WebRTC PeerConnection</title>
		<style>
			.preview{
				display: flex;
			}
			.remote{
				margin-left: 20px;
			}
		</style>
	</head>
	<body>
		<div>
			<div>
				<button id="connserver">Connect Sig Server</button>
				<button id="leave" disabled>Leave</button>
			</div>
			<div class="preview">
				<div>
					<h2>Local:</h2>
					<video id="localvideo" autoplay playsinline></video>
				</div>
				<div class="remote">
					<h2>Remote:</h2>
					<video id="remotevideo" autoplay playsinline></video>
				</div>
			</div>
		</div>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script>
		<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
		<script src="./js/client.js"></script>
	</body>
</html>
  • 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 roomid = '111111';
var socket =null;

var state = 'init';

var pc = null;

var pcConfig={
		'iceServers':[{
			'urls':'turn:121.41.76.43:3478',
			'credential':'123456',
			'username':'huang'
		}]
	}

function sendMessage(roomid,data){
	if(socket){
		socket.emit('message',roomid,data);
	}
}

function getAnswer(desc){
	pc.setLocalDescription(desc);
	sendMessage(roomid,desc);
}

function handleAnswerError(err){
	console.error('Failed to get Answer!',err);
}

function getOffer(desc){
	pc.setLocalDescription(desc);
	sendMessage(roomid,desc)
}
function handleOfferError(err){
	console.error('Failed to get Offer!',err);
}

//接收远端流通道
function call(){
	if(state === 'joined_conn'){
		if(pc){
			var options = {
				offerToReceiveAudio:1,
				offerToReceiveVideo:1
			}
			pc.createOffer(options)
			  .then(getOffer)
			  .catch(handleOfferError);
		}
	}	
}

// 第一步:开始服务
function connSignalServer(){
	//开启本地视频
	start();
	return true;
}

function conn(){
	//1 触发socke连接
	socket = io.connect();
	
	//2 加入房间后的回调
	socket.on('joined',(roomid,id)=>{
		
		state = 'joined';
		
		createPeerConnection();
		
		btnConn.disabled = true;
		btnLeave.disabled =false;
		
		console.log("reveive joined message:state=",state);	
	});
	socket.on('otherjoin',(roomid,id)=>{
		
		if (state === 'joined_unbind') {
			createPeerConnection();
		}
		state = 'joined_conn';
		
		//媒体协商
		call();
		console.log("reveive otherjoin message:state=",state);	
	});
	socket.on('full',(roomid,id)=>{
		console.log('receive full message ', roomid, id);

		closePeerConnection();
		closeLocalMedia();
		
		state = 'leaved';
		
		btnConn.disabled = false;
		btnLeave.disabled = true;
		console.log("reveive full message:state=",state);
		alert("the room is full!");
	});
	
	socket.on('leaved',(roomid,id)=>{
		
		state = 'leaved';
		socket.disconnect();
		btnConn.disabled = false;
		btnLeave.disabled = true;
		console.log("reveive leaved message:state=",state);
	});
	
	socket.on('bye',(roomid,id)=>{
		
		state = 'joined_unbind';
		closePeerConnection();
		console.log("reveive bye message:state=",state);	
	});
	socket.on('disconnect', (socket) => {
		console.log('receive disconnect message!', roomid);
		if(!(state === 'leaved')){
			closePeerConnection();
			closeLocalMedia();
		}
		state = 'leaved';
	
	});
	socket.on('message',(roomid,id,data)=>{
		
		//媒体协商
		if(data){
			if(data.type === 'offer'){
				pc.setRemoteDescription(new RTCSessionDescription(data));
				pc.createAnswer()
				  .then(getAnswer)
				  .catch(handleAnswerError);
			}else if(data.type === 'answer'){
				console.log("reveive client message=====>",data);
				pc.setRemoteDescription(new RTCSessionDescription(data));
			}else if(data.type === 'candidate'){
				var candidate = new RTCIceCandidate({
					sdpMLineIndex:data.label,
					candidate:data.candidate
				});
				pc.addIceCandidate(candidate);
				
			}else{
				console.error('the message is invalid!',data)
			}
		}
		
		console.log("reveive client message",roomid,id,data);	
	});
	
	socket.emit('join',roomid);
	return;
}

// 扑捉本地视频
function getMediaStream(stream){
	
	localStream =stream;
	//2 ===============显示本地视频===============
	localVideo.srcObject = localStream;
	
	//这个函数的调用时机特别重要 一定要在getMediaStream之后再调用,否则会出现绑定失败的情况
	conn();
}

function handleError(err){
	if(err){
		console.error("getUserMedia  error:",err);	
	}
}

// 第二步:采集本地视频
function start(){
	
	
	if (!navigator.mediaDevices||
			!navigator.mediaDevices.getUserMedia) {			
		  console.log("getUserMedia is not supported!")
		  return;
	} else {
		
		//1 ===============配置音视频参数===============
		var constraints={
			video:true,
			audio: true
		}
		
		navigator.mediaDevices.getUserMedia(constraints)
							  .then(getMediaStream)
							  .catch(handleError)
	}
}

//关闭流通道
function closeLocalMedia(){
	if (localStream&&localStream.getTracks()) {
		localStream.getTracks().forEach((track)=>{
			track.stop();	
		});
	}
	localStream = null;
}


function leave(){
	if(socket){
		socket.emit('leave',roomid);
	}
	
	//释放资源
	closePeerConnection();
	closeLocalMedia();
		
	btnConn.disabled = false;
	btnLeave.disabled = true;
}

//创建本地流媒体链接
function createPeerConnection(){
	console.log('create RTCPeerConnection!');
	if(!pc){
		pc = new RTCPeerConnection(pcConfig);
		pc.onicecandidate = (e) =>{
			if(e.candidate){
				sendMessage(roomid,{
					type:'candidate',
					label:e.candidate.sdpMLineIndex,
					id:e.candidate.sdpMid,
					candidate:e.candidate.candidate
				});
			}
		}
		pc.ontrack = (e)=>{
			remoteVideo.srcObject = e.streams[0];
		}
	}
	if(pc === null || pc === undefined){
			console.error('pc is null or undefined!');
			return;
	}

	if(localStream === null || localStream === undefined){
		console.error('localStream is null or undefined!');
		return;
	}

	if(localStream){
		localStream.getTracks().forEach((track)=>{
			pc.addTrack(track,localStream);
		})
	}
}

//关闭本地媒体流链接
function closePeerConnection(){
	console.log('close RTCPeerConnection!');
	if(pc){
		pc.close();
		pc = null;
	}
}

btnConn.onclick = connSignalServer;

btnLeave.onclick = leave;




  • 服务器源码


'use strict'

var http = require('http');
var https = require('https');
var fs = require('fs');

var express= require('express');

var serveIndex=require('serve-index');

var USERCOUNT=3;


//命令服务器
var socketIo =require('socket.io');
var log4js = require('log4js');

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(serveIndex('./public'));
app.use(express.static('./public'));

//http  server
var http_server=http.createServer(app);

//-----------------------------------------------------------------------//

var options={
	key:fs.readFileSync('./cert/3435783_huangxiaoguo.club.key'),
	cert:fs.readFileSync('./cert/3435783_huangxiaoguo.club.pem')
}

//https server
var https_server=https.createServer(options,app);

//命令服务器绑定https
var io = socketIo.listen(https_server);

io.sockets.on('connection',(socket)=>{
	logger.debug("connection");
	
	//转发信息
	socket.on('message', (room, data)=>{
		logger.debug("message data "+socket.id+" "+room,data);
		socket.to(room).emit('message', room, socket.id, data)//房间内所有人,除自己外
	});
	
	//用户加入
	socket.on('join',(room)=>{
		logger.debug("join",",room = ", room,",socket.id = ", socket.id);
		socket.join(room);
		var myRoom = io.sockets.adapter.rooms[room];
		var users =(myRoom)?Object.keys(myRoom.sockets).length:0;
		logger.debug('the number of user in 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.to(room).emit('joined',room,socket.id);
		//给房间所有人回
		// io.in(room).emit('joined',room,socket.id);
		//除自己所有站点回
		// socket.broadcast.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;
		//users-1
		logger.debug('the number of user in room is:'+(users-1));
		
		socket.to(room).emit('bye',room,socket.id);
		socket.emit('leaved',room,socket.id);
		
		//给本人回信息
		//socket.emit('leaved',room,socket.id);
		//给房间除自己以外所有人回
		//socket.to(room).emit('leaved',room,socket.id)
		//给房间所有人回
		//io.in(room).emit('leaved',room,socket.id);
		//除自己所有站点回
		//socket.broadcast.emit('leaved',room,socket.id)
	});
});

https_server.listen(443, '0.0.0.0');
http_server.listen(80,'0.0.0.0');


测试地址:https://www.huangxiaoguo.club/peerconnection/room.html

发布了316 篇原创文章 · 获赞 660 · 访问量 122万+

猜你喜欢

转载自blog.csdn.net/huangxiaoguo1/article/details/104229233
今日推荐