您的位置:首页 > 编程语言 > Java开发

基于Chrome、Java、WebSocket、WebRTC实现浏览器视频通话

2013-12-12 15:32 871 查看
http://blog.csdn.net/leecho571/article/details/8146525


基于Chrome、Java、WebSocket、WebRTC实现浏览器视频通话

分类: Java HTML5 WebSocket WebRTC2012-11-04
13:04 6598人阅读 评论(14) 收藏 举报

目录(?)[+]

介绍

最近这段时间折腾了一下WebRTC,看了网上的https://apprtc.appspot.com/的例子(可能需要翻墙访问),这个例子是部署在Google
App Engine上的应用程序,依赖GAE的环境,后台的语言是python,而且还依赖Google App Engine Channel API,所以无法在本地运行,也无法扩展。费了一番功夫研读了例子的python端的源代码,决定用Java实现,Tomcat7之后开始支持WebSocket,打算用WebSocket代替Google App Engine Channel API实现前后台的通讯,在整个例子中Java+WebSocket起到的作用是负责客户端之间的通信,并不负责视频的传输,视频的传输依赖于WebRTC。

实例的特点是:

HTML5
不需要任何插件
资源占用不是很大,对服务器的开销比较小,只要客户端建立连接,视频传输完全有浏览器完成
通过JS实现,理论上只要浏览器支持WebSocket,WebRTC就能运行(目前只在Chrome测试通过,Chrome版本24.0.1312.2 dev-m)

实现

对于前端JS代码及用到的对象大家可以访问http://www.html5rocks.com/en/tutorials/webrtc/basics/查看详细的代码介绍。我在这里只介绍下我改动过的地方,首先建立一个客户端实时获取状态的连接,在GAE的例子上是通过GAE
Channel API实现,我在这里用WebSocket实现,代码:

[javascript] view
plaincopy

function openChannel() {

console.log("Opening channel.");

socket = new WebSocket(

"ws://192.168.1.102:8080/RTCApp/websocket?u=${user}");

socket.onopen = onChannelOpened;

socket.onmessage = onChannelMessage;

socket.onclose = onChannelClosed;

}

建立一个WebSocket连接,并注册相关的事件。这里通过Java实现WebSocket连接:

[java] view
plaincopy

package org.rtc.servlet;

import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.websocket.StreamInbound;

import org.apache.catalina.websocket.WebSocketServlet;

import org.rtc.websocket.WebRTCMessageInbound;

@WebServlet(urlPatterns = { "/websocket"})

public class WebRTCWebSocketServlet extends WebSocketServlet {

private static final long serialVersionUID = 1L;

private String user;

public void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

this.user = request.getParameter("u");

super.doGet(request, response);

}

@Override

protected StreamInbound createWebSocketInbound(String subProtocol) {

return new WebRTCMessageInbound(user);

}

}

如果你想实现WebSocket必须得用Tomcat7及以上版本,并且引入:catalina.jar,tomcat-coyote.jar两个JAR包,部署到Tomcat7之后得要去webapps/应用下面去删除这两个AR包否则无法启动,WebSocket访问和普通的访问最大的不同在于继承了WebSocketServlet,关于WebSocket的详细介绍大家可以访问http://redstarofsleep.iteye.com/blog/1488639,在这里就不再赘述。大家可以看看WebRTCMessageInbound这个类的实现:

[java] view
plaincopy

package org.rtc.websocket;

import java.io.IOException;

import java.nio.ByteBuffer;

import java.nio.CharBuffer;

import org.apache.catalina.websocket.MessageInbound;

import org.apache.catalina.websocket.WsOutbound;

public class WebRTCMessageInbound extends MessageInbound {

private final String user;

public WebRTCMessageInbound(String user) {

this.user = user;

}

public String getUser(){

return this.user;

}

@Override

protected void onOpen(WsOutbound outbound) {

//触发连接事件,在连接池中添加连接

WebRTCMessageInboundPool.addMessageInbound(this);

}

@Override

protected void onClose(int status) {

//触发关闭事件,在连接池中移除连接

WebRTCMessageInboundPool.removeMessageInbound(this);

}

@Override

protected void onBinaryMessage(ByteBuffer message) throws IOException {

throw new UnsupportedOperationException(

"Binary message not supported.");

}

@Override

protected void onTextMessage(CharBuffer message) throws IOException {

}

}

WebRTCMessageInbound继承了MessageInbound,并绑定了两个事件,关键的在于连接事件,将连接存放在连接池中,等客户端A发起发送信息的时候将客户端B的连接取出来发送数据,看看WebRTCMessageInboundPool这个类:

[java] view
plaincopy

package org.rtc.websocket;

import java.io.IOException;

import java.nio.CharBuffer;

import java.util.HashMap;

import java.util.Map;

public class WebRTCMessageInboundPool {

private static final Map<String,WebRTCMessageInbound > connections = new HashMap<String,WebRTCMessageInbound>();

public static void addMessageInbound(WebRTCMessageInbound inbound){

//添加连接

System.out.println("user : " + inbound.getUser() + " join..");

connections.put(inbound.getUser(), inbound);

}

public static void removeMessageInbound(WebRTCMessageInbound inbound){

//移除连接

connections.remove(inbound.getUser());

}

public static void sendMessage(String user,String message){

try {

//向特定的用户发送数据

System.out.println("send message to user : " + user + " message content : " + message);

WebRTCMessageInbound inbound = connections.get(user);

if(inbound != null){

inbound.getWsOutbound().writeTextMessage(CharBuffer.wrap(message));

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

WebRTCMessageInboundPool这个类中最重要的是sendMessage方法,向特定的用户发送数据。

大家可以看看这段代码:

[javascript] view
plaincopy

function openChannel() {

console.log("Opening channel.");

socket = new WebSocket(

"ws://192.168.1.102:8080/RTCApp/websocket?u=${user}");

socket.onopen = onChannelOpened;

socket.onmessage = onChannelMessage;

socket.onclose = onChannelClosed;

}

${user}是怎么来的呢?其实在进入这个页面之前是有段处理的:

[java] view
plaincopy

package org.rtc.servlet;

import java.io.IOException;

import java.util.UUID;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;

import org.rtc.room.WebRTCRoomManager;

@WebServlet(urlPatterns = {"/room"})

public class WebRTCRoomServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

public void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

this.doPost(request, response);

}

public void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

String r = request.getParameter("r");

if(StringUtils.isEmpty(r)){

//如果房间为空,则生成一个新的房间号

r = String.valueOf(System.currentTimeMillis());

response.sendRedirect("room?r=" + r);

}else{

Integer initiator = 1;

String user = UUID.randomUUID().toString().replace("-", "");//生成一个用户ID串

if(!WebRTCRoomManager.haveUser(r)){//第一次进入可能是没有人的,所以就要等待连接,如果有人进入了带这个房间好的页面就会发起视频通话的连接

initiator = 0;//如果房间没有人则不发送连接的请求

}

WebRTCRoomManager.addUser(r, user);//向房间中添加一个用户

String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort() + request.getContextPath() +"/";

String roomLink = basePath + "room?r=" + r;

String roomKey = r;//设置一些变量

request.setAttribute("initiator", initiator);

request.setAttribute("roomLink", roomLink);

request.setAttribute("roomKey", roomKey);

request.setAttribute("user", user);

request.getRequestDispatcher("index.jsp").forward(request, response);

}

}

}

这个是进入房间前的处理,然而客户端是怎么发起视频通话的呢?

[javascript] view
plaincopy

function initialize() {

console.log("Initializing; room=${roomKey}.");

card = document.getElementById("card");

localVideo = document.getElementById("localVideo");

miniVideo = document.getElementById("miniVideo");

remoteVideo = document.getElementById("remoteVideo");

resetStatus();

openChannel();

getUserMedia();

}

function getUserMedia() {

try {

navigator.webkitGetUserMedia({

'audio' : true,

'video' : true

}, onUserMediaSuccess, onUserMediaError);

console.log("Requested access to local media with new syntax.");

} catch (e) {

try {

navigator.webkitGetUserMedia("video,audio",

onUserMediaSuccess, onUserMediaError);

console

.log("Requested access to local media with old syntax.");

} catch (e) {

alert("webkitGetUserMedia() failed. Is the MediaStream flag enabled in about:flags?");

console.log("webkitGetUserMedia failed with exception: "

+ e.message);

}

}

}

function onUserMediaSuccess(stream) {

console.log("User has granted access to local media.");

var url = webkitURL.createObjectURL(stream);

localVideo.style.opacity = 1;

localVideo.src = url;

localStream = stream;

// Caller creates PeerConnection.

if (initiator)

maybeStart();

}

function maybeStart() {

if (!started && localStream && channelReady) {

setStatus("Connecting...");

console.log("Creating PeerConnection.");

createPeerConnection();

console.log("Adding local stream.");

pc.addStream(localStream);

started = true;

// Caller initiates offer to peer.

if (initiator)

doCall();

}

}

function doCall() {

console.log("Sending offer to peer.");

if (isRTCPeerConnection) {

pc.createOffer(setLocalAndSendMessage, null, mediaConstraints);

} else {

var offer = pc.createOffer(mediaConstraints);

pc.setLocalDescription(pc.SDP_OFFER, offer);

sendMessage({

type : 'offer',

sdp : offer.toSdp()

});

pc.startIce();

}

}

function setLocalAndSendMessage(sessionDescription) {

pc.setLocalDescription(sessionDescription);

sendMessage(sessionDescription);

}

function sendMessage(message) {

var msgString = JSON.stringify(message);

console.log('发出信息 : ' + msgString);

path = 'message?r=${roomKey}' + '&u=${user}';

var xhr = new XMLHttpRequest();

xhr.open('POST', path, true);

xhr.send(msgString);

}

页面加载完之后会调用initialize方法,initialize方法中调用了getUserMedia方法,这个方法是通过本地摄像头获取视频的方法,在成功获取视频之后发送连接请求,并在客户端建立连接管道,最后通过sendMessage向另外一个客户端发送连接的请求,参数为当前通话的房间号和当前登陆人,下图是连接产生的日志:



[java] view
plaincopy

package org.rtc.servlet;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import javax.servlet.ServletException;

import javax.servlet.ServletInputStream;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONObject;

import org.rtc.room.WebRTCRoomManager;

import org.rtc.websocket.WebRTCMessageInboundPool;

@WebServlet(urlPatterns = {"/message"})

public class WebRTCMessageServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

public void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

super.doPost(request, response);

}

public void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

String r = request.getParameter("r");//房间号

String u = request.getParameter("u");//通话人

BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream)request.getInputStream()));

String line = null;

StringBuilder sb = new StringBuilder();

while((line = br.readLine())!=null){

sb.append(line); //获取输入流,主要是视频定位的信息

}

String message = sb.toString();

JSONObject json = JSONObject.fromObject(message);

if (json != null) {

String type = json.getString("type");

if ("bye".equals(type)) {//客户端退出视频聊天

System.out.println("user :" + u + " exit..");

WebRTCRoomManager.removeUser(r, u);

}

}

String otherUser = WebRTCRoomManager.getOtherUser(r, u);//获取通话的对象

if (u.equals(otherUser)) {

message = message.replace("\"offer\"", "\"answer\"");

message = message.replace("a=crypto:0 AES_CM_128_HMAC_SHA1_32",

"a=xrypto:0 AES_CM_128_HMAC_SHA1_32");

message = message.replace("a=ice-options:google-ice\\r\\n", "");

}

//向对方发送连接数据

WebRTCMessageInboundPool.sendMessage(otherUser, message);

}

}

就这样通过WebSokcet向客户端发送连接数据,然后客户端根据接收到的数据进行视频接收:

[javascript] view
plaincopy

function onChannelMessage(message) {

console.log('收到信息 : ' + message.data);

if (isRTCPeerConnection)

processSignalingMessage(message.data);//建立视频连接

else

processSignalingMessage00(message.data);

}

function processSignalingMessage(message) {

var msg = JSON.parse(message);

if (msg.type === 'offer') {

// Callee creates PeerConnection

if (!initiator && !started)

maybeStart();

// We only know JSEP version after createPeerConnection().

if (isRTCPeerConnection)

pc.setRemoteDescription(new RTCSessionDescription(msg));

else

pc.setRemoteDescription(pc.SDP_OFFER,

new SessionDescription(msg.sdp));

doAnswer();

} else if (msg.type === 'answer' && started) {

pc.setRemoteDescription(new RTCSessionDescription(msg));

} else if (msg.type === 'candidate' && started) {

var candidate = new RTCIceCandidate({

sdpMLineIndex : msg.label,

candidate : msg.candidate

});

pc.addIceCandidate(candidate);

} else if (msg.type === 'bye' && started) {

onRemoteHangup();

}

}

就这样通过Java、WebSocket、WebRTC就实现了在浏览器上的视频通话。

请教

还有一个就自己的一个疑问,我定义的WebSocket失效时间是20秒,时间太短了。希望大家指教一下如何设置WebSocket的失效时间。

截图



演示地址

你可以和你的朋友一起进入http://blog.csdn.net/leecho571/article/details/8207102,感受下Ext结合WebSocket、WebRTC构建的即时通讯
建议大家将chrome升级至最新版本http://www.google.cn/intl/zh-CN/chrome/browser/eula.html?extra=devchannel&platform=win

源码下载

http://download.csdn.net/detail/leecho571/5117399

大家可以按照这种思路去自己实现,建议大家最好用Chrome浏览器进行测试。
大家可以进群:197331959进行交流。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: