Html5 点对点视频聊天 - 基于 HTML5、WebRTC、Node.js 的P2P视频聊天DEMO

2016-12-12 Linyuting.cn 个人日志

    众所周知HTML5是支持操作用户媒体摄像头、麦克风的,所以理论上我们就能进行录像和其他操作了。在本文中,我们利用Html5、WebRTC以及Node.js 实现浏览器与浏览器之间的P2P通信。

    实际效果(这里我是手机与电脑进行视频通话):

222.png

    项目准备:

        1.Node.js环境,安装必备的包 ws http express

        2.电脑带有摄像头

     使用说明:

        1.将以下三个文件放在同个目录下,运行 node server.js

        2.访问 IP地址:8080 即可看到页面,随便输入一个你能记住的用户名登陆,另一台设备(手机也可以)打开同个地址,并用另一个用户名登陆,然后又call 第一个网页,和打电话一样。

    其他说明:

        1.浏览器尽量选择新一点的,建议使用FireFox调试,因为在较新的chrome中,外部地址访问媒体必须开启https,本地地址无所谓。生产环境的话还是必须套个SSL证书上去的,开启HTTPS的话务必将websocket协议ws改为wss

        2.这里我把语音关了只留下视频,因为在测试的时候会产生无限回音,很难听,需要声音的可以自行将audio改为true

        3.目前stun服务器使用的是google的,因google域名被墙,所以这里直接使用他的IP地址



    index.html  摄像头录制以及聊天页面


<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>基于 HTML5、WebRTC、Node.js 的P2P视频聊天DEMO</title>
		<style type="text/css">
			body{
				margin-top: 15px;
			}
			video{
				background-color: #000;
				border: 1px solid gray;
			}
			.container{
				position: relative;
				display: block;
				margin: 0 auto;
				width: 500px;
				height: 500px;
			}
			#yours{
				width: 150px;
				height: 150px;
				position: absolute;
				top: 15px;
				right: 15px;
			}
			#theirs{
				width: 500px;
				height: 500px;
			}
		</style>
	</head>
	<body>
		<div id="login-page" class="page">
			<h2>Login As</h2>
			<input type="text" id="username" />
			<button id="login">Login</button>
		</div>
		<div id="call-page" class="page container">
			<video id="yours" autoplay="autoplay"></video>
			<video id="theirs" autoplay="autoplay"></video>
			<input type="text" id="their-username" />
			<button id="call">Call</button>
			<button id="hang-up">Hang Up</button>
		</div>
		<script type="text/javascript" src="client.js"></script>
	</body>
</html>


client.js  基本页面控制以及WebRTC客户端实现

var name = '', connectedUser;

var loginPage = document.querySelector('#login-page'),
	usernameInput = document.querySelector('#username'),
	loginButton = document.querySelector('#login'),
	callPage = document.querySelector('#call-page'),
	theirUsernameInput = document.querySelector('#their-username'),
	callButton = document.querySelector('#call'),
	hangUpButton = document.querySelector('#hang-up');
	callPage.style.display = 'none';
loginButton.addEventListener('click', function(){
	name = usernameInput.value;
	if(name.length>0){
		send({
			type: "login",
			name: name
		});
	}
});


var connection = new WebSocket('ws://'+document.domain+':8888');
connection.onopen = function(){
	console.log('Connected.');
};
connection.onmessage = function(message){
	console.log('Got message', message.data);
	var data = JSON.parse(message.data);
	switch(data.type){
		case 'login':
			onLogin(data.success);
			break;
		case 'offer':
			onOffer(data.offer, data.name);
			break;
		case 'answer':
			onAnswer(data.answer);
			break;
		case 'candidate':
			onCandidate(data.candidate);
			break;
		case 'leave':
			onLeave();
			break;
		default:
			break;
	}
};
connection.onerror = function(err){
	console.log("Got error", err);
};

function send(message){
	if(connectedUser){
		message.name = connectedUser;
	}
	connection.send(JSON.stringify(message));
}


function onLogin(success){
	if(success = false){
		alert('Login failed, Please try a different name,');
	}else{
		loginPage.style.display = 'none';
		callPage.style.display = 'block';
		startConnection();
	}
};

callButton.addEventListener('click', function(){
	var theirUsername = theirUsernameInput.value;
	if(theirUsername.length>0){
		startPeerConnection(theirUsername);
	}
});

hangUpButton.addEventListener('click', function(){
	send({
		type: "leave"
	});
	onLeave();
});

function onOffer(offer, name){
	connectedUser = name;
	yourConnection.setRemoteDescription(new RTCSessionDescription(offer));
	yourConnection.createAnswer(function(_answer){
		yourConnection.setLocalDescription(_answer);
		send({
			type: 'answer',
			answer: _answer
		});
	}, function(err){
		console.log('An error occur on onOffer.');
	});
};

function onAnswer(answer){
	yourConnection.setRemoteDescription(new RTCSessionDescription(answer));
};

function onCandidate(candidate){
	yourConnection.addIceCandidate(new RTCIceCandidate(candidate));
};

function onLeave(){
	connectedUser = null;
	theirConnection = null;
	yourConnection.close();
	yourConnection.onicecandidate = null;
	yourConnection.onaddstream = null;
	setupPeerConnection(stream);
};

function hasUserMedia(){
	return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia || navigator.mediaDevices.getUserMedia || navigator.mozGetUserMedia);
}

function hasRTCPeerConnection(){
	window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
	window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription;
	window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate;
	return !!window.RTCPeerConnection;
}
var yourVideo = document.querySelector('#yours'), 
	theirVideo = document.querySelector('#theirs'), 
	yourConnection, 
	theirConnection,
	stream;
function startConnection(){
	if(hasUserMedia()){
		navigator._getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia || navigator.mozGetUserMedia || navigator.mediaDevices.getUserMedia);
		navigator._getUserMedia(
			{
				video: true,
				audio: false
			},
			function(myStream){
				stream = myStream;
				yourVideo.src = window.URL.createObjectURL(stream);
				if(hasRTCPeerConnection()){
					setupPeerConnection(stream);
				}
				else{
					alert('Sorry, your browser does not support WebRTC.');
				}
			},
			function(error){
				console.log(error);
			}
		);
	}
};

function setupPeerConnection(stream){
	var configuration = {
        "iceServers": [{
			 "url": "stun:173.194.202.127:19302"
		}]
	};
	yourConnection = new RTCPeerConnection(configuration);
	yourConnection.addStream(stream);
	yourConnection.onaddstream = function(e){
		theirVideo.src = window.URL.createObjectURL(e.stream);
	};
	yourConnection.onicecandidate = function(e){
		if(e.candidate){
			send({
				type: "candidate",
				candidate: e.candidate
			});
		}
	};
	
};

function startPeerConnection(user){
	connectedUser = user;
	yourConnection.createOffer(function(_offer){
		send({
			type: "offer",
			offer: _offer
		});
		yourConnection.setLocalDescription(_offer);
	}, function(error){
		alert('An error on startPeerConnection:', error);
	});
};
server.js  信令服务器以及HTTP服务器脚本二合一
var WebSocketServer = require('ws').Server,
	wss = new WebSocketServer({port: 8888}),
	express = require('express'),
    app = express(),
	users = {};

app.use(express.static('./'))
app.get('/', function (req, res) {
  res.sendFile('./index.html')
});
app.listen(8080, function () {
  console.log('Example app listening on port 8080!')
})

function sendTo(conn, message){
	conn.send(JSON.stringify(message));
};

wss.on('connection', function(connection){
	connection.on('message', function(message){
		var data;
		try{
			data = JSON.parse(message);
		}
		catch(e){
			console.log('Error parsing JSON.');
			data = {};
		}
		switch(data.type){
			case 'login':
				console.log('User logged in as', data.name);
				if(users[data.name]){
					sendTo(connection,{
						type: "login",
						success: false
					});
				}else{
					users[data.name] = connection;
					connection.name = data.name;
					sendTo(connection,{
						type: "login",
						success: true
					});
				}
				break;
			case 'offer':
				console.log('Sending offer to', data.name);
				var conn = users[data.name];
				if(conn != null){
					connection.otherName = data.name;
					sendTo(conn, {
						type: "offer",
						offer: data.offer,
						name: connection.name
					});
				}
				break;
			case 'answer':
				console.log('Sending answer to', data.name);
				var conn = users[data.name];
				if(conn != null){
					connection.otherName = data.name;
					sendTo(conn, {
						type: "answer",
						answer: data.answer
					});
				}
				break;
			case 'candidate':
				console.log('Sending candidate to', data.name);
				var conn = users[data.name];
				if(conn != null){
					connection.otherName = data.name;
					sendTo(conn, {
						type: "candidate",
						candidate: data.candidate
					});
				}
				break;
			case 'leave':
				console.log('Disconnecting user from', data.name);
				var conn = users[data.name];
				if(conn != null){
					connection.otherName = data.name;
					sendTo(conn, {
						type: "leave"
					});
				}
				break;
			default:
				sendTo(conn, {
					type: "error",
					message: "Unrecognized command: " + data.type
				});
				break;
		}
	});
	connection.on('close', function(){
		if(connection.name){
			delete users[connection.name];
			if(connection.otherName){
				console.log('Disconnecting user from', connection.otherName);
				var conn = users[connection.otherName];
				conn.otherName = null;
				if(conn != null){
					sendTo(conn,{
						type: "leave"
					});
				}
			}
		}
	});
});
wss.on('listening', function(){
	console.log('Server started...');
});


参考文档:

https://www.w3.org/TR/webrtc/

https://webrtc.github.io/samples/

标签: HTML5 JavaScript WebRTC

评论:

Fantasy
2016-12-13 14:01
效果如何
Linyuting.cn
2016-12-15 13:12
@Fantasy:已经上图

发表评论:

本站由emlog驱动 粤ICP备15042739号