通过webRTC+websocket实现视频通话
最近空闲时间较多,于是写了一个个人博客,其中实现了实时通话,但是感觉功能比较无趣,前两天突发奇想,觉得如果可以视频通话就有意思了,于是从网上搜索了相关资料,发现webRTC可以实现该功能,但是网上关于webRTC的资料甚少,经过三天的研究,终于简单的实现了视频通话的功能,具体实现过程如下:1.技术点:webRTC、websocket2.实现思路:两个浏览器打开同一页面,连接到同一个socket。此
·
最近空闲时间较多,于是写了一个个人博客,其中实现了实时通话,但是感觉功能比较无趣,前两天突发奇想,觉得如果可以视频通话就有意思了,于是从网上搜索了相关资料,发现webRTC可以实现该功能,但是网上关于webRTC的资料甚少,经过三天的研究,终于简单的实现了视频通话的功能,具体实现过程如下:
1.技术点:
webRTC、websocket
2.实现思路:
两个浏览器打开同一页面,连接到同一个socket。
此时由一端点击建立连接,发起建立连接的一端就是offer(携带信号源信息),发给另外一个端,另外一个端收到offer之后,发出响应answer(携带信号源信息),offer端收到answer端信息进行存储;这样每个端都有了自己的信息和对方的信息,offer发出answer发出后设置了localDescription和remoteDescription后就会触发onicecandidate,如此一来,双方都有了对方的localDescription、remoteDescription和candidata;有了这三种数据之后,就可以触发Connection.onaddstream函数,然后通过theirVideo.srcObject = e.stream这个方法,把流写到video标签内,然后video标签里就会有对方的视频画面了。
注意:这样实现之后不能直接调用navigator.getUserMedia()函数,浏览器会默认getUserMedia为undifined,如果是互联网模式这里需要设置浏览(如果只是本地测试则不需要)(谷歌浏览器配置方法);webrtc如只是p2p不需要特别服务器,自已开发信令服务就可以啦,当要安装turn server 国内常有打洞不成功需要转发。
下面是实现代码:
1.前端页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>webrtc</title>
<style>
#yours{
width:300px;
position:absolute;
top:200px;
left:100px;
}
#theirs{
width:300px;
position:absolute;
top:200px;
left:400px;
}
</style>
</head>
<body>
<button onclick="createOffer()">开始视频</button>
<video id="yours" autoplay></video>
<video id="theirs" autoplay></video>
</body>
<script type="text/javascript" src="../blog/plugin/jquery/1.9.1/jquery.min.js"></script>
<script src="webtrc.js"></script>
<script>
$(function () {
f()
})
</script>
</html>
2.js、webRTC实现方法
var websocket;
function randomNum(minNum,maxNum){
switch(arguments.length){
case 1:
return parseInt(Math.random()*minNum+1,10);
break;
case 2:
return parseInt(Math.random()*(maxNum-minNum+1)+minNum,10);
break;
default:
return 0;
break;
}
}
const userid = 'user' + randomNum(0,100000);
function hasUserMedia() {
navigator.getUserMedia = navigator.getUserMedia || navigator.msGetUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
return !!navigator.getUserMedia;
}
function hasRTCPeerConnection() {
window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.msRTCPeerConnection;
return !!window.RTCPeerConnection;
}
var yourVideo = document.getElementById("yours");
var theirVideo = document.getElementById("theirs");
var Connection;
function startPeerConnection() {
//return;
var config = {
'iceServers': [
//{ 'urls': 'stun:stun.xten.com:3478' },
//{ 'urls': 'stun:stun.voxgratia.org:3478' },
{ 'url': 'stun:stun.l.google.com:19302' }
]
};
config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:global.stun.twilio.com:3478?transport=udp' }
],
//sdpSemantics: 'unified-plan'
};
// {
// "iceServers": [{
// "url": "stun:stun.1.google.com:19302"
// }]
// };
Connection = new RTCPeerConnection(config);
Connection.onicecandidate = function(e) {
console.log('onicecandidate');
if (e.candidate) {
websocket.send(JSON.stringify({
"userid":userid,
"event": "_ice_candidate",
"data": {
"candidate": e.candidate
}
}));
}
}
Connection.onaddstream = function(e) {
console.log('onaddstream');
//theirVideo.src = window.URL.createObjectURL(e.stream);
theirVideo.srcObject = e.stream;
}
}
createSocket();
startPeerConnection();
function f() {
navigator.getUserMedia({ video: true, audio: true },
stream => {
yourVideo.srcObject = stream;
window.stream = stream;
Connection.addStream(stream)
},
err => {
console.log(err);
})
}
function createOffer(){
//发送offer和answer的函数,发送本地session描述
Connection.createOffer().then(offer => {
Connection.setLocalDescription(offer);
websocket.send(JSON.stringify({
"userid":userid,
"event": "offer",
"data": {
"sdp": offer
}
}));
});
}
function createSocket(){
var user = Math.round(Math.random()*1000) + ""
websocket = new WebSocket("ws://127.0.0.1:8080/msgServer/"+user);
eventBind();
};
function eventBind() {
//连接成功
websocket.onopen = function(e) {
console.log('连接成功')
};
//server端请求关闭
websocket.onclose = function(e) {
console.log('close')
};
//error
websocket.onerror = function(e) {
};
//收到消息
websocket.onmessage = (event)=> {
if(event.data == "new user") {
location.reload();
} else {
var json = JSON.parse(event.data);
console.log('onmessage: ', json);
if(json.userid !=userid){
//如果是一个ICE的候选,则将其加入到PeerConnection中,否则设定对方的session描述为传递过来的描述
if(json.event === "_ice_candidate"&&json.data.candidate) {
Connection.addIceCandidate(new RTCIceCandidate(json.data.candidate));
}else if(json.event ==='offer'){
Connection.setRemoteDescription(json.data.sdp);
Connection.createAnswer().then(answer => {
Connection.setLocalDescription(answer);
console.log(window.stream)
websocket.send(JSON.stringify({
"userid":userid,
"event": "answer",
"data": {
"sdp": answer
}
}));
})
}else if(json.event ==='answer'){
Connection.setRemoteDescription(json.data.sdp);
console.log(window.stream)
}
}
}
};
}
websocket
package com.chat.api.web.restctrl;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Enumeration;
import java.util.concurrent.ConcurrentHashMap;
@ServerEndpoint("/msgServer/{userId}")
@Component
@Scope("prototype")
public class WebSocketServer {
/**
* 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
*/
private static int onlineCount = 0;
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
*/
private static ConcurrentHashMap<String, Session> webSocketMap = new ConcurrentHashMap<>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 接收userId
*/
private String userId = "";
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
this.userId = userId;
/**
* 连接被打开:向socket-map中添加session
*/
webSocketMap.put(userId, session);
System.out.println(userId + " - 连接建立成功...");
}
@OnMessage
public void onMessage(String message, Session session) {
try {
this.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
@OnError
public void onError(Session session, Throwable error) {
System.out.println("连接异常...");
error.printStackTrace();
}
@OnClose
public void onClose() {
System.out.println("连接关闭");
}
/**
* 实现服务器主动推送
*/
public void sendMessage(String message) throws IOException {
if (message.equals("心跳")){
this.session.getBasicRemote().sendText(message);
}
Enumeration<String> keys = webSocketMap.keys();
while (keys.hasMoreElements()){
String key = keys.nextElement();
if (key.equals(this.userId)){
System.err.println("my id " + key);
continue;
}
if (webSocketMap.get(key) == null){
webSocketMap.remove(key);
System.err.println(key + " : null");
continue;
}
Session sessionValue = webSocketMap.get(key);
if (sessionValue.isOpen()){
System.out.println("发消息给: " + key + " ,message: " + message);
sessionValue.getBasicRemote().sendText(message);
}else {
System.err.println(key + ": not open");
sessionValue.close();
webSocketMap.remove(key);
}
}
}
/**
* 发送自定义消息
*/
public static void sendInfo(String message, @PathParam("userId") String userId) throws IOException {
System.out.println("发送消息到:" + userId + ",内容:" + message);
if (!StringUtils.isEmpty(userId) && webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).getBasicRemote().sendText(message);
//webSocketServer.sendMessage(message);
} else {
System.out.println("用户" + userId + ",不在线!");
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
最终的实现效果如下(为了避免我的帅气伤人,这里打码( ̄ェ ̄;))
自己尝试的搭建了信令服务器,但是没有成功,已经购买了域名,备案成功后,想通过域名来实现跨网的视频通话。
更多推荐
已为社区贡献1条内容
所有评论(0)